diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bcc0dfd8f4..615d007ef5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -125,10 +125,16 @@
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
-
+
+
untrustedIdentities = new LinkedList<>();
List unregisteredUsers = new LinkedList<>();
+ List networkExceptions = new LinkedList<>();
for (PushAddress recipient : recipients) {
try {
@@ -186,11 +189,14 @@ public class TextSecureMessageSender {
} catch (UnregisteredUserException e) {
Log.w(TAG, e);
unregisteredUsers.add(e);
+ } catch (PushNetworkException e) {
+ Log.w(TAG, e);
+ networkExceptions.add(new NetworkFailureException(recipient.getNumber(), e));
}
}
- if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty()) {
- throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers);
+ if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) {
+ throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers, networkExceptions);
}
}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/SignedPreKeyEntity.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/SignedPreKeyEntity.java
index 56077f8635..663568a849 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/SignedPreKeyEntity.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/SignedPreKeyEntity.java
@@ -16,24 +16,28 @@
*/
package org.whispersystems.textsecure.api.push;
-import com.google.thoughtcrimegson.GsonBuilder;
-import com.google.thoughtcrimegson.JsonDeserializationContext;
-import com.google.thoughtcrimegson.JsonDeserializer;
-import com.google.thoughtcrimegson.JsonElement;
-import com.google.thoughtcrimegson.JsonParseException;
-import com.google.thoughtcrimegson.JsonPrimitive;
-import com.google.thoughtcrimegson.JsonSerializationContext;
-import com.google.thoughtcrimegson.JsonSerializer;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
-import org.whispersystems.textsecure.internal.util.Base64;
import org.whispersystems.textsecure.internal.push.PreKeyEntity;
+import org.whispersystems.textsecure.internal.util.Base64;
import java.io.IOException;
-import java.lang.reflect.Type;
public class SignedPreKeyEntity extends PreKeyEntity {
+ @JsonProperty
+ @JsonSerialize(using = ByteArraySerializer.class)
+ @JsonDeserialize(using = ByteArrayDeserializer.class)
private byte[] signature;
public SignedPreKeyEntity() {}
@@ -47,42 +51,18 @@ public class SignedPreKeyEntity extends PreKeyEntity {
return signature;
}
- public static String toJson(SignedPreKeyEntity entity) {
- GsonBuilder builder = new GsonBuilder();
- return forBuilder(builder).create().toJson(entity);
- }
-
- public static SignedPreKeyEntity fromJson(String serialized) {
- GsonBuilder builder = new GsonBuilder();
- return forBuilder(builder).create().fromJson(serialized, SignedPreKeyEntity.class);
- }
-
- public static GsonBuilder forBuilder(GsonBuilder builder) {
- return PreKeyEntity.forBuilder(builder)
- .registerTypeAdapter(byte[].class, new ByteArrayJsonAdapter());
-
- }
-
- private static class ByteArrayJsonAdapter
- implements JsonSerializer, JsonDeserializer
- {
+ private static class ByteArraySerializer extends JsonSerializer {
@Override
- public JsonElement serialize(byte[] signature, Type type,
- JsonSerializationContext jsonSerializationContext)
- {
- return new JsonPrimitive(Base64.encodeBytesWithoutPadding(signature));
+ public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeString(Base64.encodeBytesWithoutPadding(value));
}
+ }
+
+ private static class ByteArrayDeserializer extends JsonDeserializer {
@Override
- public byte[] deserialize(JsonElement jsonElement, Type type,
- JsonDeserializationContext jsonDeserializationContext)
- throws JsonParseException
- {
- try {
- return Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString());
- } catch (IOException e) {
- throw new JsonParseException(e);
- }
+ public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ return Base64.decodeWithoutPadding(p.getValueAsString());
}
}
}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/EncapsulatedExceptions.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/EncapsulatedExceptions.java
index 0617b59a59..dc0f15030b 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/EncapsulatedExceptions.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/EncapsulatedExceptions.java
@@ -24,12 +24,15 @@ public class EncapsulatedExceptions extends Throwable {
private final List untrustedIdentityExceptions;
private final List unregisteredUserExceptions;
+ private final List networkExceptions;
public EncapsulatedExceptions(List untrustedIdentities,
- List unregisteredUsers)
+ List unregisteredUsers,
+ List networkExceptions)
{
this.untrustedIdentityExceptions = untrustedIdentities;
this.unregisteredUserExceptions = unregisteredUsers;
+ this.networkExceptions = networkExceptions;
}
public List getUntrustedIdentityExceptions() {
@@ -39,4 +42,8 @@ public class EncapsulatedExceptions extends Throwable {
public List getUnregisteredUserExceptions() {
return unregisteredUserExceptions;
}
+
+ public List getNetworkExceptions() {
+ return networkExceptions;
+ }
}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/NetworkFailureException.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/NetworkFailureException.java
new file mode 100644
index 0000000000..bc9b42c5b3
--- /dev/null
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/NetworkFailureException.java
@@ -0,0 +1,15 @@
+package org.whispersystems.textsecure.api.push.exceptions;
+
+public class NetworkFailureException extends Exception {
+
+ private final String e164number;
+
+ public NetworkFailureException(String e164number, Exception nested) {
+ super(nested);
+ this.e164number = e164number;
+ }
+
+ public String getE164number() {
+ return e164number;
+ }
+}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/PushNetworkException.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/PushNetworkException.java
index 0c7713c591..f875ba900d 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/PushNetworkException.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/exceptions/PushNetworkException.java
@@ -19,6 +19,7 @@ package org.whispersystems.textsecure.api.push.exceptions;
import java.io.IOException;
public class PushNetworkException extends IOException {
+
public PushNetworkException(Exception exception) {
super(exception);
}
@@ -26,4 +27,5 @@ public class PushNetworkException extends IOException {
public PushNetworkException(String s) {
super(s);
}
+
}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java
index 88e72d01b4..5d32b48378 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java
@@ -16,10 +16,17 @@
*/
package org.whispersystems.textsecure.internal.push;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
public class AccountAttributes {
+ @JsonProperty
private String signalingKey;
+
+ @JsonProperty
private boolean supportsSms;
+
+ @JsonProperty
private int registrationId;
public AccountAttributes(String signalingKey, boolean supportsSms, int registrationId) {
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/DeviceCode.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/DeviceCode.java
index d2fc463ad8..fcc14df84e 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/DeviceCode.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/DeviceCode.java
@@ -1,7 +1,10 @@
package org.whispersystems.textsecure.internal.push;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
public class DeviceCode {
+ @JsonProperty
private String verificationCode;
public String getVerificationCode() {
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/MismatchedDevices.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/MismatchedDevices.java
index 0f44b7c8c7..5a459a378b 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/MismatchedDevices.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/MismatchedDevices.java
@@ -16,11 +16,15 @@
*/
package org.whispersystems.textsecure.internal.push;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
import java.util.List;
public class MismatchedDevices {
+ @JsonProperty
private List missingDevices;
+ @JsonProperty
private List extraDevices;
public List getMissingDevices() {
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/OutgoingPushMessage.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/OutgoingPushMessage.java
index 434de88ca9..845399a6df 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/OutgoingPushMessage.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/OutgoingPushMessage.java
@@ -17,14 +17,20 @@
package org.whispersystems.textsecure.internal.push;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
import org.whispersystems.textsecure.api.push.PushAddress;
import org.whispersystems.textsecure.internal.util.Base64;
public class OutgoingPushMessage {
+ @JsonProperty
private int type;
+ @JsonProperty
private int destinationDeviceId;
+ @JsonProperty
private int destinationRegistrationId;
+ @JsonProperty
private String body;
public OutgoingPushMessage(PushAddress address, int deviceId, PushBody body) {
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/OutgoingPushMessageList.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/OutgoingPushMessageList.java
index 7adcb1ddc7..6fa26eab01 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/OutgoingPushMessageList.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/OutgoingPushMessageList.java
@@ -16,16 +16,22 @@
*/
package org.whispersystems.textsecure.internal.push;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
import java.util.List;
public class OutgoingPushMessageList {
+ @JsonProperty
private String destination;
+ @JsonProperty
private String relay;
+ @JsonProperty
private long timestamp;
+ @JsonProperty
private List messages;
public OutgoingPushMessageList(String destination, long timestamp, String relay,
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyEntity.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyEntity.java
index ae67da9f38..7215a1eb5b 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyEntity.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyEntity.java
@@ -16,14 +16,15 @@
*/
package org.whispersystems.textsecure.internal.push;
-import com.google.thoughtcrimegson.GsonBuilder;
-import com.google.thoughtcrimegson.JsonDeserializationContext;
-import com.google.thoughtcrimegson.JsonDeserializer;
-import com.google.thoughtcrimegson.JsonElement;
-import com.google.thoughtcrimegson.JsonParseException;
-import com.google.thoughtcrimegson.JsonPrimitive;
-import com.google.thoughtcrimegson.JsonSerializationContext;
-import com.google.thoughtcrimegson.JsonSerializer;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
@@ -31,11 +32,15 @@ import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.textsecure.internal.util.Base64;
import java.io.IOException;
-import java.lang.reflect.Type;
public class PreKeyEntity {
- private int keyId;
+ @JsonProperty
+ private int keyId;
+
+ @JsonProperty
+ @JsonSerialize(using = ECPublicKeySerializer.class)
+ @JsonDeserialize(using = ECPublicKeyDeserializer.class)
private ECPublicKey publicKey;
public PreKeyEntity() {}
@@ -53,32 +58,21 @@ public class PreKeyEntity {
return publicKey;
}
- public static GsonBuilder forBuilder(GsonBuilder builder) {
- return builder.registerTypeAdapter(ECPublicKey.class, new ECPublicKeyJsonAdapter());
+ private static class ECPublicKeySerializer extends JsonSerializer {
+ @Override
+ public void serialize(ECPublicKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
+ }
}
-
- private static class ECPublicKeyJsonAdapter
- implements JsonSerializer, JsonDeserializer
- {
+ private static class ECPublicKeyDeserializer extends JsonDeserializer {
@Override
- public JsonElement serialize(ECPublicKey preKeyPublic, Type type,
- JsonSerializationContext jsonSerializationContext)
- {
- return new JsonPrimitive(Base64.encodeBytesWithoutPadding(preKeyPublic.serialize()));
- }
-
- @Override
- public ECPublicKey deserialize(JsonElement jsonElement, Type type,
- JsonDeserializationContext jsonDeserializationContext)
- throws JsonParseException
- {
+ public ECPublicKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
- return Curve.decodePoint(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
- } catch (InvalidKeyException | IOException e) {
- throw new JsonParseException(e);
+ return Curve.decodePoint(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
+ } catch (InvalidKeyException e) {
+ throw new IOException(e);
}
}
}
-
}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyResponse.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyResponse.java
index 7b50d4ea82..1db05f80a1 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyResponse.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyResponse.java
@@ -16,26 +16,32 @@
*/
package org.whispersystems.textsecure.internal.push;
-import com.google.thoughtcrimegson.GsonBuilder;
-import com.google.thoughtcrimegson.JsonDeserializationContext;
-import com.google.thoughtcrimegson.JsonDeserializer;
-import com.google.thoughtcrimegson.JsonElement;
-import com.google.thoughtcrimegson.JsonParseException;
-import com.google.thoughtcrimegson.JsonPrimitive;
-import com.google.thoughtcrimegson.JsonSerializationContext;
-import com.google.thoughtcrimegson.JsonSerializer;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.textsecure.internal.util.Base64;
+import org.whispersystems.textsecure.internal.util.JsonUtil;
import java.io.IOException;
-import java.lang.reflect.Type;
import java.util.List;
public class PreKeyResponse {
- private IdentityKey identityKey;
+ @JsonProperty
+ @JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
+ @JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
+ private IdentityKey identityKey;
+
+ @JsonProperty
private List devices;
public IdentityKey getIdentityKey() {
@@ -46,36 +52,5 @@ public class PreKeyResponse {
return devices;
}
- public static PreKeyResponse fromJson(String serialized) {
- GsonBuilder builder = new GsonBuilder();
- return PreKeyResponseItem.forBuilder(builder)
- .registerTypeAdapter(IdentityKey.class, new IdentityKeyJsonAdapter())
- .create().fromJson(serialized, PreKeyResponse.class);
- }
-
- public static class IdentityKeyJsonAdapter
- implements JsonSerializer, JsonDeserializer
- {
- @Override
- public JsonElement serialize(IdentityKey identityKey, Type type,
- JsonSerializationContext jsonSerializationContext)
- {
- return new JsonPrimitive(Base64.encodeBytesWithoutPadding(identityKey.serialize()));
- }
-
- @Override
- public IdentityKey deserialize(JsonElement jsonElement, Type type,
- JsonDeserializationContext jsonDeserializationContext)
- throws JsonParseException
- {
- try {
- return new IdentityKey(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
- } catch (InvalidKeyException | IOException e) {
- throw new JsonParseException(e);
- }
- }
- }
-
-
}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyResponseItem.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyResponseItem.java
index 9400a2e093..6a5fe32ae0 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyResponseItem.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyResponseItem.java
@@ -16,15 +16,22 @@
*/
package org.whispersystems.textsecure.internal.push;
-import com.google.thoughtcrimegson.GsonBuilder;
+import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
public class PreKeyResponseItem {
+ @JsonProperty
private int deviceId;
+
+ @JsonProperty
private int registrationId;
+
+ @JsonProperty
private SignedPreKeyEntity signedPreKey;
+
+ @JsonProperty
private PreKeyEntity preKey;
public int getDeviceId() {
@@ -43,7 +50,4 @@ public class PreKeyResponseItem {
return preKey;
}
- public static GsonBuilder forBuilder(GsonBuilder builder) {
- return SignedPreKeyEntity.forBuilder(builder);
- }
}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyState.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyState.java
index cb19cb9c95..d3d88a9aed 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyState.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyState.java
@@ -1,17 +1,29 @@
package org.whispersystems.textsecure.internal.push;
-import com.google.thoughtcrimegson.GsonBuilder;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
+import org.whispersystems.textsecure.internal.util.JsonUtil;
import java.util.List;
public class PreKeyState {
+ @JsonProperty
+ @JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
+ @JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey identityKey;
+
+ @JsonProperty
private List preKeys;
+
+ @JsonProperty
private PreKeyEntity lastResortKey;
+
+ @JsonProperty
private SignedPreKeyEntity signedPreKey;
@@ -24,10 +36,4 @@ public class PreKeyState {
this.identityKey = identityKey;
}
- public static String toJson(PreKeyState state) {
- GsonBuilder builder = new GsonBuilder();
- return SignedPreKeyEntity.forBuilder(builder)
- .registerTypeAdapter(IdentityKey.class, new PreKeyResponse.IdentityKeyJsonAdapter())
- .create().toJson(state);
- }
}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyStatus.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyStatus.java
index 652f1ca417..cd53f71813 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyStatus.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PreKeyStatus.java
@@ -16,8 +16,11 @@
*/
package org.whispersystems.textsecure.internal.push;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
public class PreKeyStatus {
+ @JsonProperty
private int count;
public PreKeyStatus() {}
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java
index b303e99415..942f788f10 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java
@@ -18,8 +18,7 @@ package org.whispersystems.textsecure.internal.push;
import android.util.Log;
-import com.google.thoughtcrimegson.Gson;
-import com.google.thoughtcrimegson.JsonParseException;
+import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.whispersystems.libaxolotl.IdentityKey;
@@ -44,6 +43,7 @@ import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesE
import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException;
import org.whispersystems.textsecure.internal.util.Base64;
import org.whispersystems.textsecure.internal.util.BlacklistingTrustManager;
+import org.whispersystems.textsecure.internal.util.JsonUtil;
import org.whispersystems.textsecure.internal.util.Util;
import java.io.File;
@@ -115,17 +115,17 @@ public class PushServiceSocket {
{
AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, supportsSms, registrationId);
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode),
- "PUT", new Gson().toJson(signalingKeyEntity));
+ "PUT", JsonUtil.toJson(signalingKeyEntity));
}
public String getNewDeviceVerificationCode() throws IOException {
String responseText = makeRequest(PROVISIONING_CODE_PATH, "GET", null);
- return new Gson().fromJson(responseText, DeviceCode.class).getVerificationCode();
+ return JsonUtil.fromJson(responseText, DeviceCode.class).getVerificationCode();
}
public void sendProvisioningMessage(String destination, byte[] body) throws IOException {
makeRequest(String.format(PROVISIONING_MESSAGE_PATH, destination), "PUT",
- new Gson().toJson(new ProvisioningMessage(Base64.encodeBytes(body))));
+ JsonUtil.toJson(new ProvisioningMessage(Base64.encodeBytes(body))));
}
public void sendReceipt(String destination, long messageId, String relay) throws IOException {
@@ -140,7 +140,7 @@ public class PushServiceSocket {
public void registerGcmId(String gcmRegistrationId) throws IOException {
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId, true);
- makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration));
+ makeRequest(REGISTER_GCM_PATH, "PUT", JsonUtil.toJson(registration));
}
public void unregisterGcmId() throws IOException {
@@ -151,11 +151,10 @@ public class PushServiceSocket {
throws IOException
{
try {
- String responseText = makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", new Gson().toJson(bundle));
+ String responseText = makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle));
if (responseText == null) return new SendMessageResponse(false);
- else return new Gson().fromJson(responseText, SendMessageResponse.class);
-
+ else return JsonUtil.fromJson(responseText, SendMessageResponse.class);
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(bundle.getDestination(), nfe);
}
@@ -184,13 +183,13 @@ public class PushServiceSocket {
signedPreKey.getSignature());
makeRequest(String.format(PREKEY_PATH, ""), "PUT",
- PreKeyState.toJson(new PreKeyState(entities, lastResortEntity,
- signedPreKeyEntity, identityKey)));
+ JsonUtil.toJson(new PreKeyState(entities, lastResortEntity,
+ signedPreKeyEntity, identityKey)));
}
public int getAvailablePreKeys() throws IOException {
String responseText = makeRequest(PREKEY_METADATA_PATH, "GET", null);
- PreKeyStatus preKeyStatus = new Gson().fromJson(responseText, PreKeyStatus.class);
+ PreKeyStatus preKeyStatus = JsonUtil.fromJson(responseText, PreKeyStatus.class);
return preKeyStatus.getCount();
}
@@ -209,7 +208,7 @@ public class PushServiceSocket {
}
String responseText = makeRequest(path, "GET", null);
- PreKeyResponse response = PreKeyResponse.fromJson(responseText);
+ PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
List bundles = new LinkedList<>();
for (PreKeyResponseItem device : response.getDevices()) {
@@ -236,7 +235,7 @@ public class PushServiceSocket {
}
return bundles;
- } catch (JsonParseException e) {
+ } catch (JsonUtil.JsonParseException e) {
throw new IOException(e);
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(destination.getNumber(), nfe);
@@ -253,7 +252,7 @@ public class PushServiceSocket {
}
String responseText = makeRequest(path, "GET", null);
- PreKeyResponse response = PreKeyResponse.fromJson(responseText);
+ PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
if (response.getDevices() == null || response.getDevices().size() < 1)
throw new IOException("Empty prekey list");
@@ -278,7 +277,7 @@ public class PushServiceSocket {
return new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId, preKey,
signedPreKeyId, signedPreKey, signedPreKeySignature, response.getIdentityKey());
- } catch (JsonParseException e) {
+ } catch (JsonUtil.JsonParseException e) {
throw new IOException(e);
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(destination.getNumber(), nfe);
@@ -288,7 +287,7 @@ public class PushServiceSocket {
public SignedPreKeyEntity getCurrentSignedPreKey() throws IOException {
try {
String responseText = makeRequest(SIGNED_PREKEY_PATH, "GET", null);
- return SignedPreKeyEntity.fromJson(responseText);
+ return JsonUtil.fromJson(responseText, SignedPreKeyEntity.class);
} catch (NotFoundException e) {
Log.w("PushServiceSocket", e);
return null;
@@ -299,12 +298,12 @@ public class PushServiceSocket {
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
signedPreKey.getKeyPair().getPublicKey(),
signedPreKey.getSignature());
- makeRequest(SIGNED_PREKEY_PATH, "PUT", SignedPreKeyEntity.toJson(signedPreKeyEntity));
+ makeRequest(SIGNED_PREKEY_PATH, "PUT", JsonUtil.toJson(signedPreKeyEntity));
}
public long sendAttachment(PushAttachmentData attachment) throws IOException {
String response = makeRequest(String.format(ATTACHMENT_PATH, ""), "GET", null);
- AttachmentDescriptor attachmentKey = new Gson().fromJson(response, AttachmentDescriptor.class);
+ AttachmentDescriptor attachmentKey = JsonUtil.fromJson(response, AttachmentDescriptor.class);
if (attachmentKey == null || attachmentKey.getLocation() == null) {
throw new IOException("Server failed to allocate an attachment key!");
@@ -326,7 +325,7 @@ public class PushServiceSocket {
}
String response = makeRequest(path, "GET", null);
- AttachmentDescriptor descriptor = new Gson().fromJson(response, AttachmentDescriptor.class);
+ AttachmentDescriptor descriptor = JsonUtil.fromJson(response, AttachmentDescriptor.class);
Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + descriptor.getLocation());
@@ -337,8 +336,8 @@ public class PushServiceSocket {
throws NonSuccessfulResponseCodeException, PushNetworkException
{
ContactTokenList contactTokenList = new ContactTokenList(new LinkedList<>(contactTokens));
- String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", new Gson().toJson(contactTokenList));
- ContactTokenDetailsList activeTokens = new Gson().fromJson(response, ContactTokenDetailsList.class);
+ String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", JsonUtil.toJson(contactTokenList));
+ ContactTokenDetailsList activeTokens = JsonUtil.fromJson(response, ContactTokenDetailsList.class);
return activeTokens.getContacts();
}
@@ -346,7 +345,7 @@ public class PushServiceSocket {
public ContactTokenDetails getContactTokenDetails(String contactToken) throws IOException {
try {
String response = makeRequest(String.format(DIRECTORY_VERIFY_PATH, contactToken), "GET", null);
- return new Gson().fromJson(response, ContactTokenDetails.class);
+ return JsonUtil.fromJson(response, ContactTokenDetails.class);
} catch (NotFoundException nfe) {
return null;
}
@@ -463,14 +462,14 @@ public class PushServiceSocket {
} catch (IOException e) {
throw new PushNetworkException(e);
}
- throw new MismatchedDevicesException(new Gson().fromJson(response, MismatchedDevices.class));
+ throw new MismatchedDevicesException(JsonUtil.fromJson(response, MismatchedDevices.class));
case 410:
try {
response = Util.readFully(connection.getErrorStream());
} catch (IOException e) {
throw new PushNetworkException(e);
}
- throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.class));
+ throw new StaleDevicesException(JsonUtil.fromJson(response, StaleDevices.class));
case 417:
throw new ExpectationFailedException();
}
@@ -524,9 +523,7 @@ public class PushServiceSocket {
return connection;
} catch (IOException e) {
throw new PushNetworkException(e);
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e);
- } catch (KeyManagementException e) {
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new AssertionError(e);
}
}
@@ -540,7 +537,11 @@ public class PushServiceSocket {
}
private static class GcmRegistrationId {
+
+ @JsonProperty
private String gcmRegistrationId;
+
+ @JsonProperty
private boolean webSocketChannel;
public GcmRegistrationId() {}
@@ -552,7 +553,10 @@ public class PushServiceSocket {
}
private static class AttachmentDescriptor {
+ @JsonProperty
private long id;
+
+ @JsonProperty
private String location;
public long getId() {
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/StaleDevices.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/StaleDevices.java
index 34039dee40..6ea74ce2ee 100644
--- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/StaleDevices.java
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/push/StaleDevices.java
@@ -16,10 +16,13 @@
*/
package org.whispersystems.textsecure.internal.push;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
import java.util.List;
public class StaleDevices {
+ @JsonProperty
private List staleDevices;
public List getStaleDevices() {
diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/JsonUtil.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/JsonUtil.java
new file mode 100644
index 0000000000..65e066f66b
--- /dev/null
+++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/JsonUtil.java
@@ -0,0 +1,75 @@
+package org.whispersystems.textsecure.internal.util;
+
+import android.util.Log;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.InvalidKeyException;
+
+import java.io.IOException;
+
+public class JsonUtil {
+
+ private static final String TAG = JsonUtil.class.getSimpleName();
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ static {
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ public static String toJson(Object object) {
+ try {
+ return objectMapper.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ Log.w(TAG, e);
+ return "";
+ }
+ }
+
+ public static T fromJson(String json, Class clazz) {
+ try {
+ return objectMapper.readValue(json, clazz);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ throw new JsonParseException(e);
+ }
+ }
+
+ public static class JsonParseException extends RuntimeException {
+ public JsonParseException(Exception e) {
+ super(e);
+ }
+ }
+
+ public static class IdentityKeySerializer extends JsonSerializer {
+ @Override
+ public void serialize(IdentityKey value, JsonGenerator gen, SerializerProvider serializers)
+ throws IOException
+ {
+ gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
+ }
+ }
+
+ public static class IdentityKeyDeserializer extends JsonDeserializer {
+ @Override
+ public IdentityKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ try {
+ return new IdentityKey(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
+ } catch (InvalidKeyException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+
+}
diff --git a/res/drawable-xxhdpi/ic_error_red_24dp.png b/res/drawable-xxhdpi/ic_error_red_24dp.png
new file mode 100644
index 0000000000..0a272ad41a
Binary files /dev/null and b/res/drawable-xxhdpi/ic_error_red_24dp.png differ
diff --git a/res/drawable-xxhdpi/ic_error_white_18dp.png b/res/drawable-xxhdpi/ic_error_white_18dp.png
new file mode 100644
index 0000000000..036d5bed33
Binary files /dev/null and b/res/drawable-xxhdpi/ic_error_white_18dp.png differ
diff --git a/res/drawable-xxhdpi/ic_info_outline_grey600_24dp.png b/res/drawable-xxhdpi/ic_info_outline_grey600_24dp.png
new file mode 100644
index 0000000000..af621e2da1
Binary files /dev/null and b/res/drawable-xxhdpi/ic_info_outline_grey600_24dp.png differ
diff --git a/res/drawable-xxhdpi/ic_refresh_white_18dp.png b/res/drawable-xxhdpi/ic_refresh_white_18dp.png
new file mode 100644
index 0000000000..fe4285f679
Binary files /dev/null and b/res/drawable-xxhdpi/ic_refresh_white_18dp.png differ
diff --git a/res/drawable/error_round.xml b/res/drawable/error_round.xml
new file mode 100644
index 0000000000..56cc75291a
--- /dev/null
+++ b/res/drawable/error_round.xml
@@ -0,0 +1,16 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/info_round.xml b/res/drawable/info_round.xml
new file mode 100644
index 0000000000..668eaf3bee
--- /dev/null
+++ b/res/drawable/info_round.xml
@@ -0,0 +1,16 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index 9705373dea..578436bfdf 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -47,7 +47,7 @@
@@ -111,7 +111,7 @@
+ android:contentDescription="@string/conversation_item_sent__send_failed_indicator_description" />
+ android:contentDescription="@string/conversation_item_sent__pending_approval_description"
+ tools:visibility="visible" />
+
+ android:textSize="16sp"
+ tools:text="Lorem ipsum mango dolor coconut papaya" />
diff --git a/res/layout/message_details_activity.xml b/res/layout/message_details_activity.xml
new file mode 100644
index 0000000000..39603606f5
--- /dev/null
+++ b/res/layout/message_details_activity.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/res/layout/message_details_header.xml b/res/layout/message_details_header.xml
new file mode 100644
index 0000000000..c4ecaeea35
--- /dev/null
+++ b/res/layout/message_details_header.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/message_details_recipient.xml b/res/layout/message_details_recipient.xml
new file mode 100644
index 0000000000..c5c2648ccb
--- /dev/null
+++ b/res/layout/message_details_recipient.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3a699f216e..520956e0aa 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -17,4 +17,5 @@
12dp
3
+ 10dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c93ccb4d8c..49eb1ae585 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -59,10 +59,20 @@
Audio
Contact info
+
+ The
+ identifying key material for %1$s has changed. This could either mean that someone is trying to
+ intercept your communication, or that %2$s simply re-installed TextSecure and now has a new
+ identity key.
+
+ You may wish to verify
+ this contact.
+
+
Message size: %d KB
Expires: %s
- Error sending message
+ Not delivered
View secure media?
This media has been stored in an encrypted database. Unfortunately, to view it with an external content viewer currently requires the data to be temporarily decrypted and written to disk. Are you sure that you would like to do this?
Error, received stale key exchange message.
@@ -70,6 +80,7 @@
%1$s has left the group.
Tap for SMS fallback
Tap for MMS fallback
+ Tap for details
Tap for unsecured fallback
Fallback to SMS?
Fallback to MMS?
@@ -126,8 +137,8 @@
Unable to write to storage!
Saving attachment
Saving attachment to storage...
- PENDING
- PUSH
+ Pending...
+ Data (TextSecure)
MMS
SMS
Deleting...
@@ -225,6 +236,10 @@
Install Barcode Scanner?
TextSecure needs Barcode Scanner for QR codes.
+
+ Failed to send
+ New identity
+
Error storing MMS!
Error connecting to MMS provider...
@@ -642,6 +657,14 @@
Their identity (they read):
Your identity (you read):
+
+ Sent
+ Received
+ Via
+ To:
+ From:
+ With:
+
Create passphrase
Enter passphrase
diff --git a/res/values/styles.xml b/res/values/styles.xml
index bd743c937f..b318195b27 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -161,4 +161,19 @@
- #ff999999
+
+
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index d197acaa3d..83afee2db6 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -72,9 +72,7 @@ public class ApplicationContext extends Application implements DependencyInjecto
return jobManager;
}
-
private void initializeRandomNumberFix() {
- Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
PRNGFixes.apply();
}
diff --git a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java
new file mode 100644
index 0000000000..63c413fe5d
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java
@@ -0,0 +1,216 @@
+package org.thoughtcrime.securesms;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.view.View;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.IdentityDatabase;
+import org.thoughtcrime.securesms.database.MmsAddressDatabase;
+import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.database.MmsSmsDatabase;
+import org.thoughtcrime.securesms.database.PushDatabase;
+import org.thoughtcrime.securesms.database.SmsDatabase;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
+import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.jobs.PushDecryptJob;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.sms.MessageSender;
+import org.thoughtcrime.securesms.util.Base64;
+import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
+import org.whispersystems.textsecure.internal.push.PushMessageProtos;
+
+import java.io.IOException;
+
+public class ConfirmIdentityDialog extends AlertDialog {
+
+ private static final String TAG = ConfirmIdentityDialog.class.getSimpleName();
+
+ private OnClickListener callback;
+
+ public ConfirmIdentityDialog(Context context,
+ MasterSecret masterSecret,
+ MessageRecord messageRecord,
+ IdentityKeyMismatch mismatch)
+ {
+ super(context);
+ Recipient recipient = RecipientFactory.getRecipientForId(context, mismatch.getRecipientId(), false);
+ String name = recipient.toShortString();
+ String introduction = String.format(context.getString(R.string.ConfirmIdentityDialog_the_signature_on_this_key_exchange_is_different), name, name);
+ SpannableString spannableString = new SpannableString(introduction + " " +
+ context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_this_contact));
+
+ spannableString.setSpan(new VerifySpan(context, masterSecret, mismatch),
+ introduction.length()+1, spannableString.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ setTitle(name);
+ setMessage(spannableString);
+
+ setButton(AlertDialog.BUTTON_POSITIVE, "Accept", new AcceptListener(masterSecret, messageRecord, mismatch));
+ setButton(AlertDialog.BUTTON_NEGATIVE, "Cancel", new CancelListener());
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ ((TextView)this.findViewById(android.R.id.message))
+ .setMovementMethod(LinkMovementMethod.getInstance());
+ }
+
+ public void setCallback(OnClickListener callback) {
+ this.callback = callback;
+ }
+
+ private class AcceptListener implements OnClickListener {
+
+ private final MasterSecret masterSecret;
+ private final MessageRecord messageRecord;
+ private final IdentityKeyMismatch mismatch;
+
+ private AcceptListener(MasterSecret masterSecret, MessageRecord messageRecord, IdentityKeyMismatch mismatch) {
+ this.masterSecret = masterSecret;
+ this.messageRecord = messageRecord;
+ this.mismatch = mismatch;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ new AsyncTask()
+ {
+ @Override
+ protected Void doInBackground(Void... params) {
+ IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
+
+ identityDatabase.saveIdentity(masterSecret,
+ mismatch.getRecipientId(),
+ mismatch.getIdentityKey());
+
+ processMessageRecord(messageRecord);
+ processPendingMessageRecords(messageRecord.getThreadId(), mismatch);
+
+ return null;
+ }
+
+ private void processMessageRecord(MessageRecord messageRecord) {
+ if (messageRecord.isOutgoing()) processOutgoingMessageRecord(messageRecord);
+ else processIncomingMessageRecord(messageRecord);
+ }
+
+ private void processPendingMessageRecords(long threadId, IdentityKeyMismatch mismatch) {
+ MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(getContext());
+ Cursor cursor = mmsSmsDatabase.getIdentityConflictMessagesForThread(threadId);
+ MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(cursor, masterSecret);
+ MessageRecord record;
+
+ try {
+ while ((record = reader.getNext()) != null) {
+ for (IdentityKeyMismatch recordMismatch : record.getIdentityKeyMismatches()) {
+ if (mismatch.equals(recordMismatch)) {
+ processMessageRecord(record);
+ }
+ }
+ }
+ } finally {
+ if (reader != null)
+ reader.close();
+ }
+ }
+
+ private void processOutgoingMessageRecord(MessageRecord messageRecord) {
+ SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
+ MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
+ MmsAddressDatabase mmsAddressDatabase = DatabaseFactory.getMmsAddressDatabase(getContext());
+
+ if (messageRecord.isMms()) {
+ mmsDatabase.removeMismatchedIdentity(messageRecord.getId(),
+ mismatch.getRecipientId(),
+ mismatch.getIdentityKey());
+
+ Recipients recipients = mmsAddressDatabase.getRecipientsForId(messageRecord.getId());
+
+ if (recipients.isGroupRecipient()) MessageSender.resendGroupMessage(getContext(), masterSecret, messageRecord, mismatch.getRecipientId());
+ else MessageSender.resend(getContext(), masterSecret, messageRecord);
+ } else {
+ smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
+ mismatch.getRecipientId(),
+ mismatch.getIdentityKey());
+
+ MessageSender.resend(getContext(), masterSecret, messageRecord);
+ }
+ }
+
+ private void processIncomingMessageRecord(MessageRecord messageRecord) {
+ try {
+ PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getContext());
+ SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
+
+ smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
+ mismatch.getRecipientId(),
+ mismatch.getIdentityKey());
+
+ TextSecureEnvelope envelope = new TextSecureEnvelope(PushMessageProtos.IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE,
+ messageRecord.getIndividualRecipient().getNumber(),
+ messageRecord.getRecipientDeviceId(), "",
+ messageRecord.getDateSent(),
+ Base64.decode(messageRecord.getBody().getBody()));
+
+ long pushId = pushDatabase.insert(envelope);
+
+ ApplicationContext.getInstance(getContext())
+ .getJobManager()
+ .add(new PushDecryptJob(getContext(), pushId, messageRecord.getId(),
+ messageRecord.getIndividualRecipient().getNumber()));
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ }.execute();
+
+ if (callback != null) callback.onClick(null, 0);
+ }
+ }
+
+ private class CancelListener implements OnClickListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (callback != null) callback.onClick(null, 0);
+ }
+ }
+
+ private static class VerifySpan extends ClickableSpan {
+ private final Context context;
+ private final MasterSecret masterSecret;
+ private final IdentityKeyMismatch mismatch;
+
+ private VerifySpan(Context context, MasterSecret masterSecret, IdentityKeyMismatch mismatch) {
+ this.context = context;
+ this.masterSecret = masterSecret;
+ this.mismatch = mismatch;
+ }
+
+ @Override
+ public void onClick(View widget) {
+ Intent intent = new Intent(context, VerifyIdentityActivity.class);
+ intent.putExtra("recipient", mismatch.getRecipientId());
+ intent.putExtra("master_secret", masterSecret);
+ intent.putExtra("remote_identity", new IdentityKeyParcelable(mismatch.getIdentityKey()));
+ context.startActivity(intent);
+ }
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java
index 4ab6830e44..015f51582e 100644
--- a/src/org/thoughtcrime/securesms/ConversationAdapter.java
+++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java
@@ -62,7 +62,6 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
private final Set batchSelected = Collections.synchronizedSet(new HashSet());
private final SelectionClickListener selectionClickListener;
- private final Handler failedIconClickHandler;
private final Context context;
private final MasterSecret masterSecret;
private final boolean groupThread;
@@ -70,13 +69,12 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
private final LayoutInflater inflater;
public ConversationAdapter(Context context, MasterSecret masterSecret, SelectionClickListener selectionClickListener,
- Handler failedIconClickHandler, boolean groupThread, boolean pushDestination)
+ boolean groupThread, boolean pushDestination)
{
super(context, null, 0);
this.context = context;
this.masterSecret = masterSecret;
this.selectionClickListener = selectionClickListener;
- this.failedIconClickHandler = failedIconClickHandler;
this.groupThread = groupThread;
this.pushDestination = pushDestination;
this.inflater = LayoutInflater.from(context);
@@ -90,7 +88,7 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
item.set(masterSecret, messageRecord, batchSelected, selectionClickListener,
- failedIconClickHandler, groupThread, pushDestination);
+ groupThread, pushDestination);
}
@Override
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index a87b2e628c..9ed1b88cf3 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -8,7 +8,6 @@ import android.content.Intent;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
@@ -16,7 +15,6 @@ import android.support.v4.widget.CursorAdapter;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.view.ActionMode;
import android.text.ClipboardManager;
-import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -30,6 +28,7 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@@ -44,8 +43,6 @@ import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
-import java.sql.Date;
-import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;
@@ -103,7 +100,6 @@ public class ConversationFragment extends ListFragment
private void initializeListAdapter() {
if (this.recipients != null && this.threadId != -1) {
this.setListAdapter(new ConversationAdapter(getActivity(), masterSecret, selectionClickListener,
- new FailedIconClickHandler(),
(!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(),
DirectoryHelper.isPushDestination(getActivity(), this.recipients)));
getListView().setRecyclerListener((ConversationAdapter)getListAdapter());
@@ -218,46 +214,11 @@ public class ConversationFragment extends ListFragment
}
private void handleDisplayDetails(MessageRecord message) {
- long dateReceived = message.getDateReceived();
- long dateSent = message.getDateSent();
-
- String transport;
-
- if (message.isPending()) transport = getString(R.string.ConversationFragment_pending);
- else if (message.isPush()) transport = getString(R.string.ConversationFragment_push);
- else if (message.isMms()) transport = getString(R.string.ConversationFragment_mms);
- else transport = getString(R.string.ConversationFragment_sms);
-
- String dateFormatPattern;
-
- if (DateFormat.is24HourFormat(getActivity().getApplicationContext())) {
- dateFormatPattern = "EEE MMM d, yyyy '-' HH:mm:ss zzz";
- } else {
- dateFormatPattern = "EEE MMM d, yyyy '-' hh:mm:ss a zzz";
- }
-
- SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormatPattern);
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(R.string.ConversationFragment_message_details);
- builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_info_icon));
- builder.setCancelable(true);
-
- if (dateReceived == dateSent || message.isOutgoing()) {
- builder.setMessage(String.format(getActivity()
- .getString(R.string.ConversationFragment_transport_s_sent_received_s),
- transport,
- dateFormatter.format(new Date(dateSent))));
- } else {
- builder.setMessage(String.format(getActivity()
- .getString(R.string.ConversationFragment_sender_s_transport_s_sent_s_received_s),
- message.getIndividualRecipient().getNumber(),
- transport,
- dateFormatter.format(new Date(dateSent)),
- dateFormatter.format(new Date(dateReceived))));
- }
-
- builder.setPositiveButton(android.R.string.ok, null);
- builder.show();
+ Intent intent = new Intent(getActivity(), MessageDetailsActivity.class);
+ intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret);
+ intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, message.getId());
+ intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, message.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT);
+ startActivity(intent);
}
private void handleForwardMessage(MessageRecord message) {
@@ -315,15 +276,6 @@ public class ConversationFragment extends ListFragment
((CursorAdapter)getListAdapter()).changeCursor(null);
}
- private class FailedIconClickHandler extends Handler {
- @Override
- public void handleMessage(android.os.Message message) {
- if (listener != null) {
- listener.setComposeText((String)message.obj);
- }
- }
- }
-
public interface ConversationFragmentListener {
public void setComposeText(String text);
}
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index 87525fe6ef..e527ec45a6 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@@ -119,7 +120,6 @@ public class ConversationItem extends LinearLayout {
private FutureTaskListener slideDeckListener;
private TypedArray backgroundDrawables;
- private final FailedIconClickListener failedIconClickListener = new FailedIconClickListener();
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener();
private final ClickListener clickListener = new ClickListener();
@@ -158,20 +158,18 @@ public class ConversationItem extends LinearLayout {
this.backgroundDrawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
setOnClickListener(clickListener);
- if (failedImage != null) failedImage.setOnClickListener(failedIconClickListener);
if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
if (mmsThumbnail != null) mmsThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
}
public void set(MasterSecret masterSecret, MessageRecord messageRecord,
Set batchSelected, SelectionClickListener selectionClickListener,
- Handler failedIconHandler, boolean groupThread, boolean pushDestination)
+ boolean groupThread, boolean pushDestination)
{
this.masterSecret = masterSecret;
this.messageRecord = messageRecord;
this.batchSelected = batchSelected;
this.selectionClickListener = selectionClickListener;
- this.failedIconHandler = failedIconHandler;
this.groupThread = groupThread;
this.pushDestination = pushDestination;
@@ -223,10 +221,10 @@ public class ConversationItem extends LinearLayout {
if (messageRecord.isOutgoing()) {
final int background;
final int triangleBackground;
- if (messageRecord.isPending() && pushDestination && !messageRecord.isForcedSms()) {
+ if ((messageRecord.isPending() || messageRecord.isFailed()) && pushDestination && !messageRecord.isForcedSms()) {
background = SENT_PUSH_PENDING;
triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
- } else if (messageRecord.isPending() || messageRecord.isPendingSmsFallback()) {
+ } else if (messageRecord.isPending() || messageRecord.isFailed() || messageRecord.isPendingSmsFallback()) {
background = SENT_SMS_PENDING;
triangleBackground = SENT_SMS_PENDING_TRIANGLE;
} else if (messageRecord.isPush()) {
@@ -279,10 +277,9 @@ public class ConversationItem extends LinearLayout {
private void setStatusIcons(MessageRecord messageRecord) {
failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE);
- if (messageRecord.isOutgoing()) {
- pendingIndicator.setVisibility(messageRecord.isPendingSmsFallback() ? View.VISIBLE : View.GONE);
- indicatorText.setVisibility(messageRecord.isPendingSmsFallback() ? View.VISIBLE : View.GONE);
- }
+// pendingIndicator.setVisibility(View.GONE);
+ if (messageRecord.isOutgoing()) indicatorText.setVisibility(View.GONE);
+
secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE);
bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, messageRecord.isKeyExchange() ? R.drawable.ic_menu_login : 0, 0);
deliveryImage.setVisibility(!messageRecord.isKeyExchange() && messageRecord.isDelivered() ? View.VISIBLE : View.GONE);
@@ -291,25 +288,37 @@ public class ConversationItem extends LinearLayout {
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.GONE);
- if (messageRecord.isFailed()) {
- dateText.setText(R.string.ConversationItem_error_sending_message);
- } else if (messageRecord.isPendingSmsFallback() && indicatorText != null) {
- dateText.setText("");
- if (messageRecord.isPendingSecureSmsFallback()) {
- if (messageRecord.isMms()) indicatorText.setText(R.string.ConversationItem_click_to_approve_mms);
- else indicatorText.setText(R.string.ConversationItem_click_to_approve_sms);
- } else {
- indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
- }
- } else if (messageRecord.isPending()) {
- dateText.setText(" ··· ");
+ if (messageRecord.isFailed()) setFailedStatusIcons();
+ else if (messageRecord.isPendingSmsFallback()) setFallbackStatusIcons();
+ else if (messageRecord.isPending()) dateText.setText(" ··· ");
+ else setSentStatusIcons();
+
+ }
+
+ private void setSentStatusIcons() {
+ final long timestamp;
+
+ if (messageRecord.isPush()) timestamp = messageRecord.getDateSent();
+ else timestamp = messageRecord.getDateReceived();
+
+ dateText.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), timestamp));
+ }
+
+ private void setFailedStatusIcons() {
+ dateText.setText(R.string.ConversationItem_error_not_delivered);
+ indicatorText.setText(R.string.ConversationItem_click_for_details);
+ indicatorText.setVisibility(View.VISIBLE);
+ }
+
+ private void setFallbackStatusIcons() {
+ pendingIndicator.setVisibility(View.VISIBLE);
+ indicatorText.setVisibility(View.VISIBLE);
+
+ if (messageRecord.isPendingSecureSmsFallback()) {
+ if (messageRecord.isMms()) indicatorText.setText(R.string.ConversationItem_click_to_approve_mms);
+ else indicatorText.setText(R.string.ConversationItem_click_to_approve_sms);
} else {
- final long timestamp;
-
- if (messageRecord.isPush()) timestamp = messageRecord.getDateSent();
- else timestamp = messageRecord.getDateReceived();
-
- dateText.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), timestamp));
+ indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
}
}
@@ -323,7 +332,9 @@ public class ConversationItem extends LinearLayout {
}
private void setEvents(MessageRecord messageRecord) {
- setClickable(messageRecord.isPendingSmsFallback() ||
+ setClickable(messageRecord.isPendingSmsFallback() ||
+ messageRecord.hasNetworkFailures() ||
+ messageRecord.isIdentityMismatchFailure() ||
(messageRecord.isKeyExchange() &&
!messageRecord.isCorruptedKeyExchange() &&
!messageRecord.isOutgoing()));
@@ -529,7 +540,7 @@ public class ConversationItem extends LinearLayout {
private class MmsDownloadClickListener implements View.OnClickListener {
public void onClick(View v) {
NotificationMmsMessageRecord notificationRecord = (NotificationMmsMessageRecord)messageRecord;
- Log.w("MmsDownloadClickListener", "Content location: " + new String(notificationRecord.getContentLocation()));
+ Log.w(TAG, "Content location: " + new String(notificationRecord.getContentLocation()));
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE);
@@ -562,13 +573,22 @@ public class ConversationItem extends LinearLayout {
private class ClickListener implements View.OnClickListener {
public void onClick(View v) {
- if (messageRecord.isKeyExchange() &&
- !messageRecord.isOutgoing() &&
- !messageRecord.isProcessedKeyExchange() &&
- !messageRecord.isStaleKeyExchange())
+ if (messageRecord.isIdentityMismatchFailure() || messageRecord.hasNetworkFailures()) {
+ Intent intent = new Intent(context, MessageDetailsActivity.class);
+ intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret);
+ intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, messageRecord.getId());
+ intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, messageRecord.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT);
+ intent.putExtra(MessageDetailsActivity.PUSH_EXTRA, pushDestination);
+ context.startActivity(intent);
+ } else if (messageRecord.isKeyExchange() &&
+ !messageRecord.isOutgoing() &&
+ !messageRecord.isProcessedKeyExchange() &&
+ !messageRecord.isStaleKeyExchange())
+ {
handleKeyExchangeClicked();
- else if (messageRecord.isPendingSmsFallback())
+ } else if (messageRecord.isPendingSmsFallback()) {
handleMessageApproval();
+ }
}
}
diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java
index ea84da2871..4f8edb9e8c 100644
--- a/src/org/thoughtcrime/securesms/ConversationListItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationListItem.java
@@ -17,17 +17,9 @@
package org.thoughtcrime.securesms;
import android.content.Context;
-import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Typeface;
-import android.net.Uri;
import android.os.Handler;
-import android.provider.Contacts.Intents;
-import android.provider.ContactsContract.QuickContact;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
@@ -39,6 +31,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Emoji;
+import org.thoughtcrime.securesms.util.RecipientViewUtil;
import java.util.Set;
@@ -66,7 +59,6 @@ public class ConversationListItem extends RelativeLayout
private TextView subjectView;
private TextView fromView;
private TextView dateView;
- private long count;
private boolean read;
private ImageView contactPhotoImage;
@@ -98,12 +90,11 @@ public class ConversationListItem extends RelativeLayout
this.selectedThreads = selectedThreads;
this.recipients = thread.getRecipients();
this.threadId = thread.getThreadId();
- this.count = thread.getCount();
this.read = thread.isRead();
this.distributionType = thread.getDistributionType();
this.recipients.addListener(this);
- this.fromView.setText(formatFrom(recipients, count, read));
+ this.fromView.setText(RecipientViewUtil.formatFrom(context, recipients, read));
this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(),
Emoji.EMOJI_SMALL,
@@ -118,7 +109,7 @@ public class ConversationListItem extends RelativeLayout
}
setBackground(read, batchMode);
- setContactPhoto(this.recipients.getPrimaryRecipient());
+ RecipientViewUtil.setContactPhoto(context, contactPhotoImage, recipients.getPrimaryRecipient(), true);
}
public void unbind() {
@@ -130,28 +121,6 @@ public class ConversationListItem extends RelativeLayout
contactPhotoImage.setVisibility(View.VISIBLE);
}
- private void setContactPhoto(final Recipient recipient) {
- if (recipient == null) return;
-
- contactPhotoImage.setImageBitmap(recipient.getContactPhoto());
-
- if (!recipient.isGroupRecipient()) {
- contactPhotoImage.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (recipient.getContactUri() != null) {
- QuickContact.showQuickContact(context, contactPhotoImage, recipient.getContactUri(), QuickContact.MODE_LARGE, null);
- } else {
- Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null));
- context.startActivity(intent);
- }
- }
- });
- } else {
- contactPhotoImage.setOnClickListener(null);
- }
- }
-
private void setBackground(boolean read, boolean batch) {
int[] attributes = new int[]{R.attr.conversation_list_item_background_selected,
R.attr.conversation_list_item_background_read,
@@ -170,38 +139,6 @@ public class ConversationListItem extends RelativeLayout
drawables.recycle();
}
- private CharSequence formatFrom(Recipients from, long count, boolean read) {
- int attributes[] = new int[] {R.attr.conversation_list_item_count_color};
- TypedArray colors = context.obtainStyledAttributes(attributes);
-
- final String fromString;
- final boolean isUnnamedGroup = from.isGroupRecipient() && TextUtils.isEmpty(from.getPrimaryRecipient().getName());
- if (isUnnamedGroup) {
- fromString = context.getString(R.string.ConversationActivity_unnamed_group);
- } else {
- fromString = from.toShortString();
- }
- SpannableStringBuilder builder = new SpannableStringBuilder(fromString);
-
-
- final int typeface;
- if (isUnnamedGroup) {
- if (!read) typeface = Typeface.BOLD_ITALIC;
- else typeface = Typeface.ITALIC;
- } else if (!read) {
- typeface = Typeface.BOLD;
- } else {
- typeface = Typeface.NORMAL;
- }
-
- builder.setSpan(new StyleSpan(typeface), 0, builder.length(),
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
-
-
- colors.recycle();
- return builder;
- }
-
public Recipients getRecipients() {
return recipients;
}
@@ -219,8 +156,8 @@ public class ConversationListItem extends RelativeLayout
handler.post(new Runnable() {
@Override
public void run() {
- ConversationListItem.this.fromView.setText(formatFrom(recipients, count, read));
- setContactPhoto(ConversationListItem.this.recipients.getPrimaryRecipient());
+ ConversationListItem.this.fromView.setText(RecipientViewUtil.formatFrom(context, recipients, read));
+ RecipientViewUtil.setContactPhoto(context, contactPhotoImage, recipients.getPrimaryRecipient(), true);
}
});
}
diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
new file mode 100644
index 0000000000..d13a443904
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
@@ -0,0 +1,255 @@
+/**
+ * Copyright (C) 2015 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
+import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.database.MmsSmsDatabase;
+import org.thoughtcrime.securesms.database.SmsDatabase;
+import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader;
+import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.DateUtils;
+import org.thoughtcrime.securesms.util.DirectoryHelper;
+import org.thoughtcrime.securesms.util.GroupUtil;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks {
+ private final static String TAG = MessageDetailsActivity.class.getSimpleName();
+
+ public final static String MASTER_SECRET_EXTRA = "master_secret";
+ public final static String MESSAGE_ID_EXTRA = "message_id";
+ public final static String TYPE_EXTRA = "type";
+ public final static String PUSH_EXTRA = "push";
+
+ private MasterSecret masterSecret;
+ private ConversationItem conversationItem;
+ private ViewGroup itemParent;
+ private TextView sentDate;
+ private TextView receivedDate;
+ private View receivedContainer;
+ private TextView transport;
+ private TextView toFrom;
+ private ListView recipientsList;
+ private LayoutInflater inflater;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.message_details_activity);
+
+ initializeResources();
+
+ getSupportLoaderManager().initLoader(0, null, this);
+ }
+
+ private void initializeResources() {
+ inflater = LayoutInflater.from(this);
+ View header = inflater.inflate(R.layout.message_details_header, recipientsList, false);
+
+ masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
+ itemParent = (ViewGroup) findViewById(R.id.item_container );
+ recipientsList = (ListView ) findViewById(R.id.recipients_list);
+ sentDate = (TextView ) header.findViewById(R.id.sent_time);
+ receivedContainer = header.findViewById(R.id.received_container);
+ receivedDate = (TextView ) header.findViewById(R.id.received_time);
+ transport = (TextView ) header.findViewById(R.id.transport);
+ toFrom = (TextView ) header.findViewById(R.id.tofrom);
+ recipientsList.setHeaderDividersEnabled(false);
+ recipientsList.addHeaderView(header, null, false);
+ }
+
+ private void updateTransport(MessageRecord messageRecord) {
+ final String transportText;
+ if (messageRecord.isOutgoing() && messageRecord.isFailed()) {
+ transportText = "-";
+ } else if (messageRecord.isPending()) {
+ transportText = getString(R.string.ConversationFragment_pending);
+ } else if (messageRecord.isPush()) {
+ transportText = getString(R.string.ConversationFragment_push);
+ } else if (messageRecord.isMms()) {
+ transportText = getString(R.string.ConversationFragment_mms);
+ } else {
+ transportText = getString(R.string.ConversationFragment_sms);
+ }
+
+ transport.setText(transportText);
+ }
+
+ private void updateTime(MessageRecord messageRecord) {
+ if (messageRecord.isPending() || messageRecord.isFailed()) {
+ sentDate.setText("-");
+ receivedContainer.setVisibility(View.GONE);
+ } else {
+ SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this);
+ sentDate.setText(dateFormatter.format(new Date(messageRecord.getDateSent())));
+
+ if (messageRecord.getDateReceived() != messageRecord.getDateSent() && !messageRecord.isOutgoing()) {
+ receivedDate.setText(dateFormatter.format(new Date(messageRecord.getDateReceived())));
+ receivedContainer.setVisibility(View.VISIBLE);
+ } else {
+ receivedContainer.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void updateRecipients(MessageRecord messageRecord, Recipients recipients) {
+ final int toFromRes;
+ if (messageRecord.isMms() && !messageRecord.isPush() && !messageRecord.isOutgoing()) {
+ toFromRes = R.string.message_details_header__with;
+ } else if (messageRecord.isOutgoing()) {
+ toFromRes = R.string.message_details_header__to;
+ } else {
+ toFromRes = R.string.message_details_header__from;
+ }
+ toFrom.setText(toFromRes);
+ conversationItem.set(masterSecret, messageRecord, new HashSet(), null,
+ recipients != messageRecord.getRecipients(),
+ DirectoryHelper.isPushDestination(this, recipients));
+ recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, masterSecret, messageRecord, recipients));
+ }
+
+ private void inflateMessageViewIfAbsent(MessageRecord messageRecord) {
+ if (conversationItem == null) {
+ if (messageRecord.isGroupAction()) {
+ conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_activity, itemParent, false);
+ } else if (messageRecord.isOutgoing()) {
+ conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_sent, itemParent, false);
+ } else {
+ conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_received, itemParent, false);
+ }
+ itemParent.addView(conversationItem);
+ }
+ }
+
+ private MessageRecord getMessageRecord(Context context, Cursor cursor, String type) {
+ switch (type) {
+ case MmsSmsDatabase.SMS_TRANSPORT:
+ EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
+ SmsDatabase.Reader reader = smsDatabase.readerFor(masterSecret, cursor);
+ return reader.getNext();
+ case MmsSmsDatabase.MMS_TRANSPORT:
+ MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
+ MmsDatabase.Reader mmsReader = mmsDatabase.readerFor(masterSecret, cursor);
+ return mmsReader.getNext();
+ default:
+ throw new AssertionError("no valid message type specified");
+ }
+ }
+
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ return new MessageDetailsLoader(this, getIntent().getStringExtra(TYPE_EXTRA),
+ getIntent().getLongExtra(MESSAGE_ID_EXTRA, -1));
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, Cursor cursor) {
+ final MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA));
+ new MessageRecipientAsyncTask(this, messageRecord).execute();
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ recipientsList.setAdapter(null);
+ }
+
+ private class MessageRecipientAsyncTask extends AsyncTask {
+ private WeakReference weakContext;
+ private MessageRecord messageRecord;
+
+ public MessageRecipientAsyncTask(Context context, MessageRecord messageRecord) {
+ this.weakContext = new WeakReference<>(context);
+ this.messageRecord = messageRecord;
+ }
+
+ protected Context getContext() {
+ return weakContext.get();
+ }
+
+ @Override
+ public Recipients doInBackground(Void... voids) {
+ Context context = getContext();
+ if (context == null) {
+ Log.w(TAG, "associated context is destroyed, finishing early");
+ }
+
+ Recipients recipients;
+
+ final Recipients intermediaryRecipients;
+ if (messageRecord.isMms()) {
+ intermediaryRecipients = DatabaseFactory.getMmsAddressDatabase(context).getRecipientsForId(messageRecord.getId());
+ } else {
+ intermediaryRecipients = messageRecord.getRecipients();
+ }
+
+ if (!intermediaryRecipients.isGroupRecipient()) {
+ Log.w(TAG, "Recipient is not a group, resolving members immediately.");
+ recipients = intermediaryRecipients;
+ } else {
+ try {
+ String groupId = intermediaryRecipients.getPrimaryRecipient().getNumber();
+ recipients = DatabaseFactory.getGroupDatabase(context)
+ .getGroupMembers(GroupUtil.getDecodedId(groupId), false);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ recipients = new Recipients(new LinkedList());
+ }
+ }
+
+ return recipients;
+ }
+
+ @Override
+ public void onPostExecute(Recipients recipients) {
+ if (getContext() == null) {
+ Log.w(TAG, "AsyncTask finished with a destroyed context, leaving early.");
+ return;
+ }
+
+ inflateMessageViewIfAbsent(messageRecord);
+
+ updateRecipients(messageRecord, recipients);
+ updateTransport(messageRecord);
+ updateTime(messageRecord);
+ }
+
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java
new file mode 100644
index 0000000000..9b371ab338
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java
@@ -0,0 +1,58 @@
+package org.thoughtcrime.securesms;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.recipients.Recipients;
+
+public class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener {
+
+ private Context context;
+ private MasterSecret masterSecret;
+ private MessageRecord record;
+ private Recipients recipients;
+
+ public MessageDetailsRecipientAdapter(Context context, MasterSecret masterSecret, MessageRecord record, Recipients recipients) {
+ this.context = context;
+ this.masterSecret = masterSecret;
+ this.record = record;
+ this.recipients = recipients;
+ }
+
+ @Override
+ public int getCount() {
+ return recipients.getRecipientsList().size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return recipients.getRecipientsList().get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return recipients.getRecipientsList().get(position).getRecipientId();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(context).inflate(R.layout.message_details_recipient, parent, false);
+ }
+
+ ((MessageRecipientListItem)convertView).set(masterSecret, record, recipients, position);
+ return convertView;
+ }
+
+ @Override
+ public void onMovedToScrapHeap(View view) {
+ ((MessageRecipientListItem)view).unbind();
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java
new file mode 100644
index 0000000000..af721ebb44
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java
@@ -0,0 +1,177 @@
+/**
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
+import org.thoughtcrime.securesms.database.documents.NetworkFailure;
+import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.sms.MessageSender;
+import org.thoughtcrime.securesms.util.RecipientViewUtil;
+
+/**
+ * A simple view to show the recipients of a message
+ *
+ * @author Jake McGinty
+ */
+public class MessageRecipientListItem extends RelativeLayout
+ implements Recipient.RecipientModifiedListener
+{
+ private final static String TAG = MessageRecipientListItem.class.getSimpleName();
+
+ private Recipient recipient;
+ private TextView fromView;
+ private TextView errorDescription;
+ private Button conflictButton;
+ private Button resendButton;
+ private ImageView contactPhotoImage;
+
+ private final Handler handler = new Handler();
+
+ public MessageRecipientListItem(Context context) {
+ super(context);
+ }
+
+ public MessageRecipientListItem(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ this.fromView = (TextView) findViewById(R.id.from);
+ this.errorDescription = (TextView) findViewById(R.id.error_description);
+ this.contactPhotoImage = (ImageView) findViewById(R.id.contact_photo_image);
+ this.conflictButton = (Button) findViewById(R.id.conflict_button);
+ this.resendButton = (Button) findViewById(R.id.resend_button);
+ }
+
+ public void set(final MasterSecret masterSecret, final MessageRecord record, final Recipients recipients, final int position) {
+ recipient = recipients.getRecipientsList().get(position);
+ recipient.addListener(this);
+ fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipient));
+
+ RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipient, false);
+ setIssueIndicators(masterSecret, record);
+ }
+
+ private void setIssueIndicators(final MasterSecret masterSecret, final MessageRecord record) {
+ final NetworkFailure networkFailure = getNetworkFailure(record);
+ final IdentityKeyMismatch keyMismatch = networkFailure == null ? getKeyMismatch(record) : null;
+
+ String errorText = "";
+ if (networkFailure != null) {
+ errorText = getContext().getString(R.string.MessageDetailsRecipient_failed_to_send);
+ resendButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new ResendAsyncTask(masterSecret, record, networkFailure).execute();
+ }
+ });
+ } else if (keyMismatch != null) {
+ errorText = getContext().getString(R.string.MessageDetailsRecipient_new_identity);
+ conflictButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new ConfirmIdentityDialog(getContext(), masterSecret, record, keyMismatch).show();
+ }
+ });
+ }
+
+ errorDescription.setText(errorText);
+ errorDescription.setVisibility(TextUtils.isEmpty(errorText) ? View.GONE : View.VISIBLE);
+ resendButton.setVisibility(networkFailure != null ? View.VISIBLE : View.GONE);
+ conflictButton.setVisibility(keyMismatch != null ? View.VISIBLE : View.GONE);
+ }
+
+ private NetworkFailure getNetworkFailure(final MessageRecord record) {
+ if (record.hasNetworkFailures()) {
+ for (final NetworkFailure failure : record.getNetworkFailures()) {
+ if (failure.getRecipientId() == recipient.getRecipientId()) {
+ return failure;
+ }
+ }
+ }
+ return null;
+ }
+
+ private IdentityKeyMismatch getKeyMismatch(final MessageRecord record) {
+ if (record.isIdentityMismatchFailure()) {
+ for (final IdentityKeyMismatch mismatch : record.getIdentityKeyMismatches()) {
+ if (mismatch.getRecipientId() == recipient.getRecipientId()) {
+ return mismatch;
+ }
+ }
+ }
+ return null;
+ }
+
+ public void unbind() {
+ if (this.recipient != null) this.recipient.removeListener(this);
+ }
+
+ @Override
+ public void onModified(final Recipient recipient) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipient));
+ RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipient, false);
+ }
+ });
+ }
+
+ private class ResendAsyncTask extends AsyncTask {
+ private final MasterSecret masterSecret;
+ private final MessageRecord record;
+ private final NetworkFailure failure;
+
+ public ResendAsyncTask(MasterSecret masterSecret, MessageRecord record, NetworkFailure failure) {
+ this.masterSecret = masterSecret;
+ this.record = record;
+ this.failure = failure;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
+ mmsDatabase.removeFailure(record.getId(), failure);
+
+ if (record.getRecipients().isGroupRecipient()) {
+ MessageSender.resendGroupMessage(getContext(), masterSecret, record, failure.getRecipientId());
+ } else {
+ MessageSender.resend(getContext(), masterSecret, record);
+ }
+ return null;
+ }
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
index 79fbc68c88..ad916747b7 100644
--- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
+++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
@@ -236,7 +236,7 @@ public class ReceiveKeyActivity extends BaseActivity {
ApplicationContext.getInstance(context)
.getJobManager()
- .add(new PushDecryptJob(context, pushId, message.getSender()));
+ .add(new PushDecryptJob(context, pushId, messageId, message.getSender()));
smsDatabase.deleteMessage(messageId);
} catch (IOException e) {
diff --git a/src/org/thoughtcrime/securesms/ShareListItem.java b/src/org/thoughtcrime/securesms/ShareListItem.java
index 9efc0d7ec1..aa8769520f 100644
--- a/src/org/thoughtcrime/securesms/ShareListItem.java
+++ b/src/org/thoughtcrime/securesms/ShareListItem.java
@@ -17,20 +17,11 @@
package org.thoughtcrime.securesms;
import android.content.Context;
-import android.content.Intent;
import android.content.res.TypedArray;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.Build;
import android.os.Handler;
-import android.provider.Contacts.Intents;
-import android.provider.ContactsContract.QuickContact;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -39,11 +30,7 @@ import com.makeramen.RoundedImageView;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.util.BitmapUtil;
-import org.thoughtcrime.securesms.util.DateUtils;
-import org.thoughtcrime.securesms.util.Emoji;
-
-import java.util.Set;
+import org.thoughtcrime.securesms.util.RecipientViewUtil;
/**
* A simple view to show the recipients of an open conversation
@@ -51,7 +38,7 @@ import java.util.Set;
* @author Jake McGinty
*/
public class ShareListItem extends RelativeLayout
- implements Recipient.RecipientModifiedListener
+ implements Recipient.RecipientModifiedListener
{
private final static String TAG = ShareListItem.class.getSimpleName();
@@ -87,21 +74,16 @@ public class ShareListItem extends RelativeLayout
this.distributionType = thread.getDistributionType();
this.recipients.addListener(this);
- this.fromView.setText(formatFrom(recipients));
+ this.fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipients));
setBackground();
- setContactPhoto(this.recipients.getPrimaryRecipient());
+ RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, this.recipients.getPrimaryRecipient(), false);
}
public void unbind() {
if (this.recipients != null) this.recipients.removeListener(this);
}
- private void setContactPhoto(final Recipient recipient) {
- if (recipient == null) return;
- contactPhotoImage.setImageBitmap(recipient.getContactPhoto());
- }
-
private void setBackground() {
int[] attributes = new int[]{R.attr.conversation_list_item_background_read};
TypedArray drawables = context.obtainStyledAttributes(attributes);
@@ -111,26 +93,6 @@ public class ShareListItem extends RelativeLayout
drawables.recycle();
}
- private CharSequence formatFrom(Recipients from) {
- final String fromString;
- final boolean isUnnamedGroup = from.isGroupRecipient() && TextUtils.isEmpty(from.getPrimaryRecipient().getName());
- if (isUnnamedGroup) {
- fromString = context.getString(R.string.ConversationActivity_unnamed_group);
- } else {
- fromString = from.toShortString();
- }
- SpannableStringBuilder builder = new SpannableStringBuilder(fromString);
-
- final int typeface;
- if (isUnnamedGroup) typeface = Typeface.ITALIC;
- else typeface = Typeface.NORMAL;
-
- builder.setSpan(new StyleSpan(typeface), 0, builder.length(),
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
-
- return builder;
- }
-
public Recipients getRecipients() {
return recipients;
}
@@ -148,8 +110,8 @@ public class ShareListItem extends RelativeLayout
handler.post(new Runnable() {
@Override
public void run() {
- fromView.setText(formatFrom(recipients));
- setContactPhoto(recipients.getPrimaryRecipient());
+ fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipients));
+ RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipients.getPrimaryRecipient(), false);
}
});
}
diff --git a/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java
index 1b759566f2..3e43efcde5 100644
--- a/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java
+++ b/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java
@@ -20,9 +20,10 @@ package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.util.Log;
-import com.google.thoughtcrimegson.Gson;
+import com.fasterxml.jackson.annotation.JsonProperty;
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
+import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
@@ -30,10 +31,10 @@ import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.Curve25519;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
-import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
-import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.PreKeyStore;
+import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
+import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.util.Medium;
import java.io.File;
@@ -109,7 +110,7 @@ public class PreKeyUtil {
try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
FileOutputStream fout = new FileOutputStream(nextFile);
- fout.write(new Gson().toJson(new PreKeyIndex(id)).getBytes());
+ fout.write(JsonUtils.toJson(new PreKeyIndex(id)).getBytes());
fout.close();
} catch (IOException e) {
Log.w("PreKeyUtil", e);
@@ -120,7 +121,7 @@ public class PreKeyUtil {
try {
File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
FileOutputStream fout = new FileOutputStream(nextFile);
- fout.write(new Gson().toJson(new SignedPreKeyIndex(id)).getBytes());
+ fout.write(JsonUtils.toJson(new SignedPreKeyIndex(id)).getBytes());
fout.close();
} catch (IOException e) {
Log.w("PreKeyUtil", e);
@@ -135,7 +136,7 @@ public class PreKeyUtil {
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
} else {
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
- PreKeyIndex index = new Gson().fromJson(reader, PreKeyIndex.class);
+ PreKeyIndex index = JsonUtils.fromJson(reader, PreKeyIndex.class);
reader.close();
return index.nextPreKeyId;
}
@@ -153,7 +154,7 @@ public class PreKeyUtil {
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
} else {
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
- SignedPreKeyIndex index = new Gson().fromJson(reader, SignedPreKeyIndex.class);
+ SignedPreKeyIndex index = JsonUtils.fromJson(reader, SignedPreKeyIndex.class);
reader.close();
return index.nextSignedPreKeyId;
}
@@ -183,6 +184,7 @@ public class PreKeyUtil {
private static class PreKeyIndex {
public static final String FILE_NAME = "index.dat";
+ @JsonProperty
private int nextPreKeyId;
public PreKeyIndex() {}
@@ -195,6 +197,7 @@ public class PreKeyUtil {
private static class SignedPreKeyIndex {
public static final String FILE_NAME = "index.dat";
+ @JsonProperty
private int nextSignedPreKeyId;
public SignedPreKeyIndex() {}
diff --git a/src/org/thoughtcrime/securesms/database/Database.java b/src/org/thoughtcrime/securesms/database/Database.java
index 622f73a30c..cb11baa13c 100644
--- a/src/org/thoughtcrime/securesms/database/Database.java
+++ b/src/org/thoughtcrime/securesms/database/Database.java
@@ -25,9 +25,9 @@ import java.util.Set;
public abstract class Database {
- protected static final String ID_WHERE = "_id = ?";
- private static final String CONVERSATION_URI = "content://textsecure/thread/";
- private static final String CONVERSATION_LIST_URI = "content://textsecure/conversation-list";
+ protected static final String ID_WHERE = "_id = ?";
+ private static final String CONVERSATION_URI = "content://textsecure/thread/";
+ private static final String CONVERSATION_LIST_URI = "content://textsecure/conversation-list";
protected SQLiteOpenHelper databaseHelper;
protected final Context context;
diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
index 1f66d5c8a1..cc183160e9 100644
--- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
+++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
@@ -45,21 +45,22 @@ import ws.com.google.android.mms.ContentType;
public class DatabaseFactory {
- private static final int INTRODUCED_IDENTITIES_VERSION = 2;
- private static final int INTRODUCED_INDEXES_VERSION = 3;
- private static final int INTRODUCED_DATE_SENT_VERSION = 4;
- private static final int INTRODUCED_DRAFTS_VERSION = 5;
- private static final int INTRODUCED_NEW_TYPES_VERSION = 6;
- private static final int INTRODUCED_MMS_BODY_VERSION = 7;
- private static final int INTRODUCED_MMS_FROM_VERSION = 8;
- private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9;
- 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 INTRODUCED_DELIVERY_RECEIPTS = 13;
- private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14;
- private static final int INTRODUCED_THUMBNAILS_VERSION = 15;
- private static final int DATABASE_VERSION = 15;
+ private static final int INTRODUCED_IDENTITIES_VERSION = 2;
+ private static final int INTRODUCED_INDEXES_VERSION = 3;
+ private static final int INTRODUCED_DATE_SENT_VERSION = 4;
+ private static final int INTRODUCED_DRAFTS_VERSION = 5;
+ private static final int INTRODUCED_NEW_TYPES_VERSION = 6;
+ private static final int INTRODUCED_MMS_BODY_VERSION = 7;
+ private static final int INTRODUCED_MMS_FROM_VERSION = 8;
+ private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9;
+ 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 INTRODUCED_DELIVERY_RECEIPTS = 13;
+ private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14;
+ private static final int INTRODUCED_THUMBNAILS_VERSION = 15;
+ private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16;
+ private static final int DATABASE_VERSION = 16;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@@ -706,8 +707,14 @@ public class DatabaseFactory {
}
if (oldVersion < INTRODUCED_THUMBNAILS_VERSION) {
- db.execSQL("ALTER TABLE part ADD COLUMN thumbnail TEXT");
- db.execSQL("ALTER TABLE part ADD COLUMN aspect_ratio REAL");
+ db.execSQL("ALTER TABLE part ADD COLUMN thumbnail TEXT;");
+ db.execSQL("ALTER TABLE part ADD COLUMN aspect_ratio REAL;");
+ }
+
+ if (oldVersion < INTRODUCED_IDENTITY_COLUMN_VERSION) {
+ db.execSQL("ALTER TABLE sms ADD COLUMN mismatched_identities TEXT");
+ db.execSQL("ALTER TABLE mms ADD COLUMN mismatched_identities TEXT");
+ db.execSQL("ALTER TABLE mms ADD COLUMN network_failures TEXT");
}
db.setTransactionSuccessful();
diff --git a/src/org/thoughtcrime/securesms/database/MessagingDatabase.java b/src/org/thoughtcrime/securesms/database/MessagingDatabase.java
new file mode 100644
index 0000000000..5bfbe6d179
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/MessagingDatabase.java
@@ -0,0 +1,145 @@
+package org.thoughtcrime.securesms.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.thoughtcrime.securesms.database.documents.Document;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
+import org.thoughtcrime.securesms.util.JsonUtils;
+import org.whispersystems.libaxolotl.IdentityKey;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public abstract class MessagingDatabase extends Database implements MmsSmsColumns {
+
+ private static final String TAG = MessagingDatabase.class.getSimpleName();
+
+ public MessagingDatabase(Context context, SQLiteOpenHelper databaseHelper) {
+ super(context, databaseHelper);
+ }
+
+ protected abstract String getTableName();
+
+ public void addMismatchedIdentity(long messageId, long recipientId, IdentityKey identityKey) {
+ try {
+ addToDocument(messageId, MISMATCHED_IDENTITIES,
+ new IdentityKeyMismatch(recipientId, identityKey),
+ IdentityKeyMismatchList.class);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ public void removeMismatchedIdentity(long messageId, long recipientId, IdentityKey identityKey) {
+ try {
+ removeFromDocument(messageId, MISMATCHED_IDENTITIES,
+ new IdentityKeyMismatch(recipientId, identityKey),
+ IdentityKeyMismatchList.class);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ protected , I> void removeFromDocument(long messageId, String column, I object, Class clazz) throws IOException {
+ SQLiteDatabase database = databaseHelper.getWritableDatabase();
+ database.beginTransaction();
+
+ try {
+ D document = getDocument(database, messageId, column, clazz);
+ Iterator iterator = document.getList().iterator();
+
+ while (iterator.hasNext()) {
+ I item = iterator.next();
+
+ if (item.equals(object)) {
+ iterator.remove();
+ break;
+ }
+ }
+
+ setDocument(database, messageId, column, document);
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ protected , I> void addToDocument(long messageId, String column, final I object, Class clazz) throws IOException {
+ List list = new ArrayList() {{
+ add(object);
+ }};
+
+ addToDocument(messageId, column, list, clazz);
+ }
+
+ protected , I> void addToDocument(long messageId, String column, List objects, Class clazz) throws IOException {
+ SQLiteDatabase database = databaseHelper.getWritableDatabase();
+ database.beginTransaction();
+
+ try {
+ T document = getDocument(database, messageId, column, clazz);
+ document.getList().addAll(objects);
+ setDocument(database, messageId, column, document);
+
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ private void setDocument(SQLiteDatabase database, long messageId, String column, Document document) throws IOException {
+ ContentValues contentValues = new ContentValues();
+
+ if (document == null || document.size() == 0) {
+ contentValues.put(column, (String)null);
+ } else {
+ contentValues.put(column, JsonUtils.toJson(document));
+ }
+
+ database.update(getTableName(), contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
+ }
+
+ private D getDocument(SQLiteDatabase database, long messageId,
+ String column, Class clazz)
+ {
+ Cursor cursor = null;
+
+ try {
+ cursor = database.query(getTableName(), new String[] {column},
+ ID_WHERE, new String[] {String.valueOf(messageId)},
+ null, null, null);
+
+ if (cursor != null && cursor.moveToNext()) {
+ String document = cursor.getString(cursor.getColumnIndexOrThrow(column));
+
+ try {
+ if (!TextUtils.isEmpty(document)) {
+ return JsonUtils.fromJson(document, clazz);
+ }
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ try {
+ return clazz.newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/database/MmsAddressDatabase.java b/src/org/thoughtcrime/securesms/database/MmsAddressDatabase.java
index 02952ebed2..94bcf4646e 100644
--- a/src/org/thoughtcrime/securesms/database/MmsAddressDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsAddressDatabase.java
@@ -23,6 +23,11 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
+import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
+import org.thoughtcrime.securesms.recipients.Recipients;
+
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduHeaders;
@@ -33,6 +38,8 @@ import java.util.List;
public class MmsAddressDatabase extends Database {
+ private static final String TAG = MmsAddressDatabase.class.getSimpleName();
+
private static final String TABLE_NAME = "mms_addresses";
private static final String ID = "_id";
private static final String MMS_ID = "mms_id";
@@ -127,6 +134,25 @@ public class MmsAddressDatabase extends Database {
return results;
}
+ public Recipients getRecipientsForId(long messageId) {
+ List numbers = getAddressesForId(messageId);
+ List results = new LinkedList<>();
+
+ for (String number : numbers) {
+ if (!PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.equals(number)) {
+ try {
+ results.add(RecipientFactory.getRecipientsFromString(context, number, false)
+ .getPrimaryRecipient());
+ } catch (RecipientFormattingException e) {
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ return new Recipients(results);
+ }
+
+
public void deleteAddressesForId(long messageId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {messageId+""});
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index 4985dab763..b868932980 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import android.graphics.Bitmap;
import android.net.Uri;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -34,6 +33,10 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.documents.NetworkFailure;
+import org.thoughtcrime.securesms.database.documents.NetworkFailureList;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@@ -50,6 +53,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.GroupUtil;
+import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -59,10 +63,12 @@ import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -88,7 +94,9 @@ import static org.thoughtcrime.securesms.util.Util.canonicalizeNumberOrGroup;
// 2) How many queries do we make? calling getMediaMessageForId() from within an existing query
// seems wasteful.
-public class MmsDatabase extends Database implements MmsSmsColumns {
+public class MmsDatabase extends MessagingDatabase {
+
+ private static final String TAG = MmsDatabase.class.getSimpleName();
public static final String TABLE_NAME = "mms";
static final String DATE_SENT = "date";
@@ -119,6 +127,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
private static final String DELIVERY_TIME = "d_tm";
private static final String DELIVERY_REPORT = "d_rpt";
static final String PART_COUNT = "part_count";
+ static final String NETWORK_FAILURE = "network_failures";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
THREAD_ID + " INTEGER, " + DATE_SENT + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + MESSAGE_BOX + " INTEGER, " +
@@ -132,7 +141,8 @@ 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, " +
- RECEIPT_COUNT + " INTEGER DEFAULT 0, " + DELIVERY_REPORT + " INTEGER);";
+ RECEIPT_COUNT + " INTEGER DEFAULT 0, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " +
+ NETWORK_FAILURE + " TEXT DEFAULT NULL," + DELIVERY_REPORT + " INTEGER);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@@ -150,7 +160,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
+ RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE
};
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
@@ -164,6 +174,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
}
+ @Override
+ protected String getTableName() {
+ return TABLE_NAME;
+ }
+
public int getMessageCountForThread(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;
@@ -181,6 +196,22 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return 0;
}
+ public void addFailures(long messageId, List failure) {
+ try {
+ addToDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList.class);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ public void removeFailure(long messageId, NetworkFailure failure) {
+ try {
+ removeFromDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList.class);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+
public void incrementDeliveryReceiptCount(String address, long timestamp) {
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
@@ -319,6 +350,14 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
}
}
+ public Cursor getMessage(long messageId) {
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ Cursor cursor = db.query(TABLE_NAME, MMS_PROJECTION, ID_WHERE, new String[] {messageId+""},
+ null, null, null);
+ setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
+ return cursor;
+ }
+
public void updateResponseStatus(long messageId, int status) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
@@ -330,8 +369,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
private void updateMailboxBitmask(long id, long maskOff, long maskOn) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME +
- " SET " + MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + " )" +
- " WHERE " + ID + " = ?", new String[] {id + ""});
+ " SET " + MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + " )" +
+ " WHERE " + ID + " = ?", new String[] {id + ""});
}
public void markAsOutbox(long messageId) {
@@ -1008,13 +1047,18 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
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);
+ String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES));
+ String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE));
+
+ Recipients recipients = getRecipientsFor(address);
+ List mismatches = getMismatchedIdentities(mismatchDocument);
+ List networkFailures = getFailures(networkDocument);
ListenableFutureTask slideDeck = getSlideDeck(masterSecret, id);
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
addressDeviceId, dateSent, dateReceived, receiptCount,
- threadId, body, slideDeck, partCount, box);
+ threadId, body, slideDeck, partCount, box, mismatches, networkFailures);
}
private Recipients getRecipientsFor(String address) {
@@ -1036,6 +1080,30 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
}
}
+ private List getMismatchedIdentities(String document) {
+ if (!TextUtils.isEmpty(document)) {
+ try {
+ return JsonUtils.fromJson(document, IdentityKeyMismatchList.class).getList();
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ return new LinkedList<>();
+ }
+
+ private List getFailures(String document) {
+ if (!TextUtils.isEmpty(document)) {
+ try {
+ return JsonUtils.fromJson(document, NetworkFailureList.class).getList();
+ } catch (IOException ioe) {
+ Log.w(TAG, ioe);
+ }
+ }
+
+ return new LinkedList<>();
+ }
+
private DisplayRecord.Body getBody(Cursor cursor) {
try {
String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
index 5a09813f7f..882b05b7ce 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
@@ -11,6 +11,7 @@ public interface MmsSmsColumns {
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 final String MISMATCHED_IDENTITIES = "mismatched_identities";
public static class Types {
protected static final long TOTAL_MASK = 0xFFFFFFFF;
@@ -37,6 +38,7 @@ public interface MmsSmsColumns {
protected static final long MESSAGE_FORCE_SMS_BIT = 0x40;
// Key Exchange Information
+ protected static final long KEY_EXCHANGE_MASK = 0xFF00;
protected static final long KEY_EXCHANGE_BIT = 0x8000;
protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index 42255276ed..0aad7a2de6 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -23,8 +23,8 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log;
-import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.HashSet;
import java.util.Set;
@@ -49,13 +49,39 @@ public class MmsSmsDatabase extends Database {
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
- MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
+ MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
+ MmsSmsColumns.MISMATCHED_IDENTITIES,
+ MmsDatabase.NETWORK_FAILURE, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
- Cursor cursor = queryTables(projection, selection, order, null, null);
+ Cursor cursor = queryTables(projection, selection, selection, order, null, null);
+ setNotifyConverationListeners(cursor, threadId);
+
+ return cursor;
+ }
+
+ public Cursor getIdentityConflictMessagesForThread(long threadId) {
+ String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
+ MmsSmsColumns.THREAD_ID,
+ SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
+ MmsSmsColumns.NORMALIZED_DATE_SENT,
+ MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
+ MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
+ SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
+ MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
+ MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
+ MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
+ MmsSmsColumns.MISMATCHED_IDENTITIES,
+ MmsDatabase.NETWORK_FAILURE, TRANSPORT};
+
+ String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
+
+ String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.MISMATCHED_IDENTITIES + " IS NOT NULL";
+
+ Cursor cursor = queryTables(projection, selection, selection, order, null, null);
setNotifyConverationListeners(cursor, threadId);
return cursor;
@@ -71,12 +97,14 @@ public class MmsSmsDatabase extends Database {
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
- MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
+ MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
+ MmsSmsColumns.MISMATCHED_IDENTITIES,
+ MmsDatabase.NETWORK_FAILURE, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
- return queryTables(projection, selection, order, null, "1");
+ return queryTables(projection, selection, selection, order, null, "1");
}
public Cursor getUnread() {
@@ -89,12 +117,14 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
- MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
+ MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
+ MmsSmsColumns.MISMATCHED_IDENTITIES,
+ MmsDatabase.NETWORK_FAILURE, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
String selection = MmsSmsColumns.READ + " = 0";
- return queryTables(projection, selection, order, null, null);
+ return queryTables(projection, selection, selection, order, null, null);
}
public int getConversationCount(long threadId) {
@@ -109,7 +139,7 @@ public class MmsSmsDatabase extends Database {
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 smsSelection, String mmsSelection, 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,
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
@@ -117,7 +147,8 @@ 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,
- MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
+ MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
+ MmsDatabase.NETWORK_FAILURE, TRANSPORT};
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
@@ -126,7 +157,8 @@ 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,
- MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
+ MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
+ MmsDatabase.NETWORK_FAILURE, TRANSPORT};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
@@ -146,6 +178,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
+ mmsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
@@ -156,6 +189,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsDatabase.MESSAGE_SIZE);
mmsColumnsPresent.add(MmsDatabase.EXPIRY);
mmsColumnsPresent.add(MmsDatabase.STATUS);
+ mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE);
Set smsColumnsPresent = new HashSet();
smsColumnsPresent.add(MmsSmsColumns.ID);
@@ -165,14 +199,15 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add(MmsSmsColumns.READ);
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
+ smsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
smsColumnsPresent.add(SmsDatabase.TYPE);
smsColumnsPresent.add(SmsDatabase.SUBJECT);
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED);
smsColumnsPresent.add(SmsDatabase.STATUS);
- String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 2, MMS_TRANSPORT, selection, null, null, null);
- String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(TRANSPORT, smsProjection, smsColumnsPresent, 2, SMS_TRANSPORT, selection, null, null, null);
+ String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 2, MMS_TRANSPORT, mmsSelection, null, null, null);
+ String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(TRANSPORT, smsProjection, smsColumnsPresent, 2, SMS_TRANSPORT, smsSelection, null, null, null);
SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
String unionQuery = unionQueryBuilder.buildUnionQuery(new String[] {smsSubQuery, mmsSubQuery}, order, null);
diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
index c5bc39c837..e2ef629d8d 100644
--- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -28,6 +28,8 @@ import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
@@ -39,9 +41,13 @@ 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.JsonUtils;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Set;
import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
@@ -52,7 +58,9 @@ import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
* @author Moxie Marlinspike
*/
-public class SmsDatabase extends Database implements MmsSmsColumns {
+public class SmsDatabase extends MessagingDatabase {
+
+ private static final String TAG = SmsDatabase.class.getSimpleName();
public static final String TABLE_NAME = "sms";
public static final String PERSON = "person";
@@ -70,7 +78,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
- SERVICE_CENTER + " TEXT);";
+ MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@@ -85,7 +93,8 @@ 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, RECEIPT_COUNT
+ REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT,
+ MISMATCHED_IDENTITIES
};
private final JobManager jobManager;
@@ -95,6 +104,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
}
+ protected String getTableName() {
+ return TABLE_NAME;
+ }
+
private void updateTypeBitmask(long id, long maskOff, long maskOn) {
Log.w("MessageDatabase", "Updating ID: " + id + " to base type: " + maskOn);
@@ -162,6 +175,14 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
return 0;
}
+ public void markAsEndSession(long id) {
+ updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.END_SESSION_BIT);
+ }
+
+ public void markAsPreKeyBundle(long id) {
+ updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT);
+ }
+
public void markAsStaleKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_STALE_BIT);
}
@@ -303,9 +324,9 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
protected void updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
- TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
- "WHERE " + ID + " = ?",
- new String[] {body, messageId+""});
+ TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
+ "WHERE " + ID + " = ?",
+ new String[] {body, messageId + ""});
long threadId = getThreadIdForMessage(messageId);
@@ -487,9 +508,11 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
}
public Cursor getMessage(long messageId) {
- SQLiteDatabase db = databaseHelper.getReadableDatabase();
- return db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId+""},
- null, null, null);
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[]{messageId + ""},
+ null, null, null);
+ setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
+ return cursor;
}
public void deleteMessage(long messageId) {
@@ -516,7 +539,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
where += (" ELSE " + DATE_RECEIVED + " < " + date + " END)");
- db.delete(TABLE_NAME, where, new String[] {threadId+""});
+ db.delete(TABLE_NAME, where, new String[] {threadId + ""});
}
/*package*/ void deleteThreads(Set threadIds) {
@@ -606,14 +629,17 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
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);
+ String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES));
- return new SmsMessageRecord(context, messageId, body, recipients,
- recipients.getPrimaryRecipient(),
- addressDeviceId,
- dateSent, dateReceived, receiptCount, type,
- threadId, status);
+ List mismatches = getMismatches(mismatchDocument);
+ Recipients recipients = getRecipientsFor(address);
+ DisplayRecord.Body body = getBody(cursor);
+
+ return new SmsMessageRecord(context, messageId, body, recipients,
+ recipients.getPrimaryRecipient(),
+ addressDeviceId,
+ dateSent, dateReceived, receiptCount, type,
+ threadId, status, mismatches);
}
private Recipients getRecipientsFor(String address) {
@@ -631,6 +657,18 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
}
}
+ private List getMismatches(String document) {
+ try {
+ if (!TextUtils.isEmpty(document)) {
+ return JsonUtils.fromJson(document, IdentityKeyMismatchList.class).getList();
+ }
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+
+ return new LinkedList<>();
+ }
+
protected DisplayRecord.Body getBody(Cursor cursor) {
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
diff --git a/src/org/thoughtcrime/securesms/database/documents/Document.java b/src/org/thoughtcrime/securesms/database/documents/Document.java
new file mode 100644
index 0000000000..2b226f66da
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/documents/Document.java
@@ -0,0 +1,10 @@
+package org.thoughtcrime.securesms.database.documents;
+
+import java.util.List;
+
+public interface Document {
+
+ public int size();
+ public List getList();
+
+}
diff --git a/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatch.java b/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatch.java
new file mode 100644
index 0000000000..1d1c63f97a
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatch.java
@@ -0,0 +1,85 @@
+package org.thoughtcrime.securesms.database.documents;
+
+import android.util.Log;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+import org.thoughtcrime.securesms.util.Base64;
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.InvalidKeyException;
+
+import java.io.IOException;
+
+public class IdentityKeyMismatch {
+
+ private static final String TAG = IdentityKeyMismatch.class.getSimpleName();
+
+ @JsonProperty(value = "r")
+ private long recipientId;
+
+ @JsonProperty(value = "k")
+ @JsonSerialize(using = IdentityKeySerializer.class)
+ @JsonDeserialize(using = IdentityKeyDeserializer.class)
+ private IdentityKey identityKey;
+
+ public IdentityKeyMismatch() {}
+
+ public IdentityKeyMismatch(long recipientId, IdentityKey identityKey) {
+ this.recipientId = recipientId;
+ this.identityKey = identityKey;
+ }
+
+ public long getRecipientId() {
+ return recipientId;
+ }
+
+ public IdentityKey getIdentityKey() {
+ return identityKey;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || !(other instanceof IdentityKeyMismatch)) {
+ return false;
+ }
+
+ IdentityKeyMismatch that = (IdentityKeyMismatch)other;
+ return that.recipientId == this.recipientId && that.identityKey.equals(this.identityKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return (int)recipientId ^ identityKey.hashCode();
+ }
+
+ private static class IdentityKeySerializer extends JsonSerializer {
+ @Override
+ public void serialize(IdentityKey value, JsonGenerator jsonGenerator, SerializerProvider serializers)
+ throws IOException
+ {
+ jsonGenerator.writeString(Base64.encodeBytes(value.serialize()));
+ }
+ }
+
+ private static class IdentityKeyDeserializer extends JsonDeserializer {
+ @Override
+ public IdentityKey deserialize(JsonParser jsonParser, DeserializationContext ctxt)
+ throws IOException
+ {
+ try {
+ return new IdentityKey(Base64.decode(jsonParser.getValueAsString()), 0);
+ } catch (InvalidKeyException e) {
+ Log.w(TAG, e);
+ throw new IOException(e);
+ }
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatchList.java b/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatchList.java
new file mode 100644
index 0000000000..843ce9d449
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/documents/IdentityKeyMismatchList.java
@@ -0,0 +1,31 @@
+package org.thoughtcrime.securesms.database.documents;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class IdentityKeyMismatchList implements Document {
+
+ @JsonProperty(value = "m")
+ private List mismatches;
+
+ public IdentityKeyMismatchList() {
+ this.mismatches = new LinkedList<>();
+ }
+
+ public IdentityKeyMismatchList(List mismatches) {
+ this.mismatches = mismatches;
+ }
+
+ @Override
+ public int size() {
+ if (mismatches == null) return 0;
+ else return mismatches.size();
+ }
+
+ @Override
+ public List getList() {
+ return mismatches;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java b/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java
new file mode 100644
index 0000000000..faa3d20af1
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/documents/NetworkFailure.java
@@ -0,0 +1,32 @@
+package org.thoughtcrime.securesms.database.documents;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class NetworkFailure {
+
+ @JsonProperty(value = "r")
+ private long recipientId;
+
+ public NetworkFailure(long recipientId) {
+ this.recipientId = recipientId;
+ }
+
+ public NetworkFailure() {}
+
+ public long getRecipientId() {
+ return recipientId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || !(other instanceof NetworkFailure)) return false;
+
+ NetworkFailure that = (NetworkFailure)other;
+ return this.recipientId == that.recipientId;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int)recipientId;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/database/documents/NetworkFailureList.java b/src/org/thoughtcrime/securesms/database/documents/NetworkFailureList.java
new file mode 100644
index 0000000000..3347c42846
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/documents/NetworkFailureList.java
@@ -0,0 +1,33 @@
+package org.thoughtcrime.securesms.database.documents;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class NetworkFailureList implements Document {
+
+ @JsonProperty(value = "l")
+ private List failures;
+
+ public NetworkFailureList() {
+ this.failures = new LinkedList<>();
+ }
+
+ public NetworkFailureList(List failures) {
+ this.failures = failures;
+ }
+
+ @Override
+ public int size() {
+ if (failures == null) return 0;
+ else return failures.size();
+ }
+
+ @Override
+ @JsonIgnore
+ public List getList() {
+ return failures;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/database/loaders/MessageDetailsLoader.java b/src/org/thoughtcrime/securesms/database/loaders/MessageDetailsLoader.java
new file mode 100644
index 0000000000..df7d343f06
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/loaders/MessageDetailsLoader.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2015 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms.database.loaders;
+
+import android.content.Context;
+import android.database.Cursor;
+
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.MmsSmsDatabase;
+import org.thoughtcrime.securesms.util.AbstractCursorLoader;
+
+public class MessageDetailsLoader extends AbstractCursorLoader {
+ private final String type;
+ private final long messageId;
+
+ public MessageDetailsLoader(Context context, String type, long messageId) {
+ super(context);
+ this.type = type;
+ this.messageId = messageId;
+ }
+
+ @Override
+ public Cursor getCursor() {
+ switch (type) {
+ case MmsSmsDatabase.SMS_TRANSPORT:
+ return DatabaseFactory.getEncryptingSmsDatabase(context).getMessage(messageId);
+ case MmsSmsDatabase.MMS_TRANSPORT:
+ return DatabaseFactory.getMmsDatabase(context).getMessage(messageId);
+ default:
+ throw new AssertionError("no valid message type specified");
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
index d1e5cdeec1..72885f382a 100644
--- a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
@@ -22,6 +22,8 @@ import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.database.documents.NetworkFailure;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.mms.MediaNotFoundException;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
@@ -30,6 +32,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
+import java.util.List;
import java.util.concurrent.ExecutionException;
/**
@@ -52,10 +55,13 @@ public class MediaMmsMessageRecord extends MessageRecord {
long dateSent, long dateReceived, int deliveredCount,
long threadId, Body body,
ListenableFutureTask slideDeck,
- int partCount, long mailbox)
+ int partCount, long mailbox,
+ List mismatches,
+ List failures)
{
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
- dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, deliveredCount, mailbox);
+ dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, deliveredCount, mailbox,
+ mismatches, failures);
this.context = context.getApplicationContext();
this.partCount = partCount;
diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
index 1c223061b5..f3f2df7462 100644
--- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
@@ -19,18 +19,20 @@ package org.thoughtcrime.securesms.database.model;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
-import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
-import android.text.style.TextAppearanceSpan;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase;
+import org.thoughtcrime.securesms.database.documents.NetworkFailure;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.GroupUtil;
+import java.util.List;
+
/**
* The base class for message record models that are displayed in
* conversations, as opposed to models that are displayed in a thread list.
@@ -48,16 +50,20 @@ public abstract class MessageRecord extends DisplayRecord {
private static final int MAX_DISPLAY_LENGTH = 2000;
- private final Recipient individualRecipient;
- private final int recipientDeviceId;
- private final long id;
- private final int deliveryStatus;
- private final int receiptCount;
+ private final Recipient individualRecipient;
+ private final int recipientDeviceId;
+ private final long id;
+ private final int deliveryStatus;
+ private final int receiptCount;
+ private final List mismatches;
+ private final List networkFailures;
MessageRecord(Context context, long id, Body body, Recipients recipients,
Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, long threadId,
- int deliveryStatus, int receiptCount, long type)
+ int deliveryStatus, int receiptCount, long type,
+ List mismatches,
+ List networkFailures)
{
super(context, body, recipients, dateSent, dateReceived, threadId, type);
this.id = id;
@@ -65,6 +71,8 @@ public abstract class MessageRecord extends DisplayRecord {
this.recipientDeviceId = recipientDeviceId;
this.deliveryStatus = deliveryStatus;
this.receiptCount = receiptCount;
+ this.mismatches = mismatches;
+ this.networkFailures = networkFailures;
}
public abstract boolean isMms();
@@ -145,6 +153,10 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isPendingSmsFallbackType(type);
}
+ public boolean isIdentityMismatchFailure() {
+ return mismatches != null && !mismatches.isEmpty();
+ }
+
public boolean isPendingSecureSmsFallback() {
return SmsDatabase.Types.isPendingSecureSmsFallbackType(type);
}
@@ -181,6 +193,18 @@ public abstract class MessageRecord extends DisplayRecord {
return type;
}
+ public List getIdentityKeyMismatches() {
+ return mismatches;
+ }
+
+ public List getNetworkFailures() {
+ return networkFailures;
+ }
+
+ public boolean hasNetworkFailures() {
+ return networkFailures != null && !networkFailures.isEmpty();
+ }
+
protected SpannableString emphasisAdded(String sequence) {
SpannableString spannable = new SpannableString(sequence);
spannable.setSpan(new RelativeSizeSpan(0.9f), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
@@ -199,4 +223,5 @@ public abstract class MessageRecord extends DisplayRecord {
public int hashCode() {
return (int)getId();
}
+
}
diff --git a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
index 097a213d0f..b9829d2280 100644
--- a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
@@ -21,9 +21,13 @@ import android.text.SpannableString;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.database.documents.NetworkFailure;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
+import java.util.LinkedList;
+
/**
* Represents the message record model for MMS messages that are
* notifications (ie: they're pointers to undownloaded media).
@@ -47,7 +51,8 @@ public class NotificationMmsMessageRecord extends MessageRecord {
long expiry, int status, byte[] transactionId, long mailbox)
{
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
- dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, receiptCount, mailbox);
+ dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, receiptCount, mailbox,
+ new LinkedList(), new LinkedList());
this.contentLocation = contentLocation;
this.messageSize = messageSize;
diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
index b367029e4f..85af546bb8 100644
--- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
@@ -23,10 +23,15 @@ import android.text.SpannableString;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase;
+import org.thoughtcrime.securesms.database.documents.NetworkFailure;
+import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
+import java.util.LinkedList;
+import java.util.List;
+
/**
* The message record model which represents standard SMS messages.
*
@@ -43,10 +48,11 @@ public class SmsMessageRecord extends MessageRecord {
long dateSent, long dateReceived,
int receiptCount,
long type, long threadId,
- int status)
+ int status, List mismatches)
{
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
- dateSent, dateReceived, threadId, receiptCount, getGenericDeliveryStatus(status), type);
+ dateSent, dateReceived, threadId, receiptCount, getGenericDeliveryStatus(status), type,
+ mismatches, new LinkedList());
}
public long getType() {
@@ -55,7 +61,9 @@ public class SmsMessageRecord extends MessageRecord {
@Override
public SpannableString getDisplayBody() {
- if (isProcessedKeyExchange()) {
+ if (SmsDatabase.Types.isFailedDecryptType(type)) {
+ return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
+ } else if (isProcessedKeyExchange()) {
return new SpannableString("");
} else if (isStaleKeyExchange()) {
return emphasisAdded(context.getString(R.string.ConversationItem_error_received_stale_key_exchange_message));
@@ -73,8 +81,6 @@ public class SmsMessageRecord extends MessageRecord {
return new SpannableString("");
} else if (isKeyExchange() && !isOutgoing()) {
return emphasisAdded(context.getString(R.string.ConversationItem_received_key_exchange_message_click_to_process));
- } else if (SmsDatabase.Types.isFailedDecryptType(type)) {
- return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
} else if (SmsDatabase.Types.isDuplicateMessageType(type)) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_duplicate_message));
} else if (SmsDatabase.Types.isDecryptInProgressType(type)) {
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 052d70697c..2522a81cef 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.DuplicateMessageException;
+import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
@@ -37,13 +38,14 @@ import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
+import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.util.guava.Optional;
+import org.whispersystems.textsecure.api.crypto.TextSecureCipher;
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
-import org.whispersystems.textsecure.api.crypto.TextSecureCipher;
import ws.com.google.android.mms.MmsException;
@@ -52,14 +54,20 @@ public class PushDecryptJob extends MasterSecretJob {
public static final String TAG = PushDecryptJob.class.getSimpleName();
private final long messageId;
+ private final long smsMessageId;
- public PushDecryptJob(Context context, long messageId, String sender) {
+ public PushDecryptJob(Context context, long pushMessageId, String sender) {
+ this(context, pushMessageId, -1, sender);
+ }
+
+ public PushDecryptJob(Context context, long pushMessageId, long smsMessageId, String sender) {
super(context, JobParameters.newBuilder()
.withPersistence()
.withRequirement(new MasterSecretRequirement(context))
.withGroupId(sender)
.create());
- this.messageId = messageId;
+ this.messageId = pushMessageId;
+ this.smsMessageId = smsMessageId;
}
@Override
@@ -74,7 +82,7 @@ public class PushDecryptJob extends MasterSecretJob {
PushDatabase database = DatabaseFactory.getPushDatabase(context);
TextSecureEnvelope envelope = database.get(messageId);
- handleMessage(masterSecret, envelope);
+ handleMessage(masterSecret, envelope, smsMessageId);
database.delete(messageId);
}
@@ -88,7 +96,7 @@ public class PushDecryptJob extends MasterSecretJob {
}
- private void handleMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) {
+ private void handleMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
try {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
@@ -98,59 +106,72 @@ public class PushDecryptJob extends MasterSecretJob {
TextSecureMessage message = cipher.decrypt(envelope);
- if (message.isEndSession()) handleEndSessionMessage(masterSecret, recipientId, envelope, message);
- else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, envelope, message);
- else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, envelope, message);
- else handleTextMessage(masterSecret, envelope, message);
+ if (message.isEndSession()) handleEndSessionMessage(masterSecret, recipientId, envelope, message, smsMessageId);
+ else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, envelope, message, smsMessageId);
+ else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, envelope, message, smsMessageId);
+ else handleTextMessage(masterSecret, envelope, message, smsMessageId);
if (envelope.isPreKeyWhisperMessage()) {
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
}
} catch (InvalidVersionException e) {
Log.w(TAG, e);
- handleInvalidVersionMessage(masterSecret, envelope);
+ handleInvalidVersionMessage(masterSecret, envelope, smsMessageId);
} catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException | RecipientFormattingException e) {
Log.w(TAG, e);
- handleCorruptMessage(masterSecret, envelope);
+ handleCorruptMessage(masterSecret, envelope, smsMessageId);
} catch (NoSessionException e) {
Log.w(TAG, e);
- handleNoSessionMessage(masterSecret, envelope);
+ handleNoSessionMessage(masterSecret, envelope, smsMessageId);
} catch (LegacyMessageException e) {
Log.w(TAG, e);
- handleLegacyMessage(masterSecret, envelope);
+ handleLegacyMessage(masterSecret, envelope, smsMessageId);
} catch (DuplicateMessageException e) {
Log.w(TAG, e);
- handleDuplicateMessage(masterSecret, envelope);
+ handleDuplicateMessage(masterSecret, envelope, smsMessageId);
} catch (UntrustedIdentityException e) {
Log.w(TAG, e);
- handleUntrustedIdentityMessage(masterSecret, envelope);
+ handleUntrustedIdentityMessage(masterSecret, envelope, smsMessageId);
}
}
private void handleEndSessionMessage(MasterSecret masterSecret, long recipientId,
- TextSecureEnvelope envelope, TextSecureMessage message)
+ TextSecureEnvelope envelope, TextSecureMessage message,
+ long smsMessageId)
{
- IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(),
- envelope.getSourceDevice(),
- message.getTimestamp(),
- "", Optional.absent());
+ EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
+ IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(),
+ envelope.getSourceDevice(),
+ message.getTimestamp(),
+ "", Optional.absent());
- IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage);
- EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
- Pair messageAndThreadId = database.insertMessageInbox(masterSecret, incomingEndSessionMessage);
+ long threadId;
+
+ if (smsMessageId <= 0) {
+ IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage);
+ Pair messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage);
+ threadId = messageAndThreadId.second;
+ } else {
+ smsDatabase.markAsEndSession(smsMessageId);
+ threadId = smsDatabase.getThreadIdForMessage(smsMessageId);
+ }
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
sessionStore.deleteAllSessions(recipientId);
- SecurityEvent.broadcastSecurityUpdateEvent(context, messageAndThreadId.second);
- MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
+ MessageNotifier.updateNotification(context, masterSecret, threadId);
}
- private void handleGroupMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message) {
+ private void handleGroupMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message, long smsMessageId) {
GroupMessageProcessor.process(context, masterSecret, envelope, message);
+
+ if (smsMessageId > 0) {
+ DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId);
+ }
}
- private void handleMediaMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message)
+ private void handleMediaMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message, long smsMessageId)
throws MmsException
{
String localNumber = TextSecurePreferences.getLocalNumber(context);
@@ -174,73 +195,123 @@ public class PushDecryptJob extends MasterSecretJob {
.getJobManager()
.add(new AttachmentDownloadJob(context, messageAndThreadId.first));
+ if (smsMessageId >= 0) {
+ DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId);
+ }
+
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}
- private void handleTextMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message) {
- EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
- String body = message.getBody().isPresent() ? message.getBody().get() : "";
- IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(),
+ private void handleTextMessage(MasterSecret masterSecret, TextSecureEnvelope envelope,
+ TextSecureMessage message, long smsMessageId)
+ {
+ EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
+ String body = message.getBody().isPresent() ? message.getBody().get() : "";
+
+ if (smsMessageId > 0) {
+ database.updateBundleMessageBody(masterSecret, smsMessageId, body);
+ } else {
+ IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(),
envelope.getSourceDevice(),
message.getTimestamp(), body,
message.getGroupInfo());
- if (message.isSecure()) {
- textMessage = new IncomingEncryptedMessage(textMessage, body);
+ if (message.isSecure()) {
+ textMessage = new IncomingEncryptedMessage(textMessage, body);
+ }
+
+ Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage);
+ MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}
-
- Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage);
- MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}
- private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) {
- Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
- DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageAndThreadId.first);
+ private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
+ EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
- MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ if (smsMessageId <= 0) {
+ Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
+ smsDatabase.markAsInvalidVersionKeyExchange(messageAndThreadId.first);
+ MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ } else {
+ smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId);
+ }
}
- private void handleCorruptMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) {
- Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
- DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptFailed(messageAndThreadId.first);
+ private void handleCorruptMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
+ EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
- MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ if (smsMessageId <= 0) {
+ Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
+ smsDatabase.markAsDecryptFailed(messageAndThreadId.first);
+ MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ } else {
+ smsDatabase.markAsDecryptFailed(smsMessageId);
+ }
}
- private void handleNoSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) {
- Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
- DatabaseFactory.getEncryptingSmsDatabase(context).markAsNoSession(messageAndThreadId.first);
+ private void handleNoSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
+ EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
- MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ if (smsMessageId <= 0) {
+ Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
+ smsDatabase.markAsNoSession(messageAndThreadId.first);
+ MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ } else {
+ smsDatabase.markAsNoSession(smsMessageId);
+ }
}
- private void handleLegacyMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) {
- Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
- DatabaseFactory.getEncryptingSmsDatabase(context).markAsLegacyVersion(messageAndThreadId.first);
+ private void handleLegacyMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
+ EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
- MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ if (smsMessageId <= 0) {
+ Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
+ smsDatabase.markAsLegacyVersion(messageAndThreadId.first);
+ MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ } else {
+ smsDatabase.markAsLegacyVersion(smsMessageId);
+ }
}
- private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) {
- // Let's start ignoring these now.
-
-// Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
-// DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptDuplicate(messageAndThreadId.first);
+ private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
+ // Let's start ignoring these now
+// SmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
//
-// MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+// if (smsMessageId <= 0) {
+// Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope);
+// smsDatabase.markAsDecryptDuplicate(messageAndThreadId.first);
+// MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+// } else {
+// smsDatabase.markAsDecryptDuplicate(smsMessageId);
+// }
}
- private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) {
- String encoded = Base64.encodeBytes(envelope.getMessage());
- IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(),
- envelope.getTimestamp(), encoded,
- Optional.absent());
+ private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
+ try {
+ EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
+ Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
+ long recipientId = recipients.getPrimaryRecipient().getRecipientId();
+ PreKeyWhisperMessage whisperMessage = new PreKeyWhisperMessage(envelope.getMessage());
+ IdentityKey identityKey = whisperMessage.getIdentityKey();
+ String encoded = Base64.encodeBytes(envelope.getMessage());
+ IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(),
+ envelope.getTimestamp(), encoded,
+ Optional.absent());
- IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
- Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context)
- .insertMessageInbox(masterSecret, bundleMessage);
+ if (smsMessageId <= 0) {
+ IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
+ Pair messageAndThreadId = database.insertMessageInbox(masterSecret, bundleMessage);
- MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ database.addMismatchedIdentity(messageAndThreadId.first, recipientId, identityKey);
+ MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ } else {
+ database.updateMessageBody(masterSecret, smsMessageId, encoded);
+ database.markAsPreKeyBundle(smsMessageId);
+ database.addMismatchedIdentity(smsMessageId, recipientId, identityKey);
+ }
+ } catch (RecipientFormattingException | InvalidMessageException | InvalidVersionException e) {
+ throw new AssertionError(e);
+ }
}
private Pair insertPlaceholder(MasterSecret masterSecret, TextSecureEnvelope envelope) {
diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
index ae940304c0..9fde55bf86 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
@@ -8,13 +8,14 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
+import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.jobqueue.JobParameters;
@@ -25,9 +26,11 @@ import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
import org.whispersystems.textsecure.api.push.PushAddress;
-import org.whispersystems.textsecure.internal.push.PushMessageProtos;
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
+import org.whispersystems.textsecure.api.push.exceptions.NetworkFailureException;
+import org.whispersystems.textsecure.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
+import org.whispersystems.textsecure.internal.push.PushMessageProtos;
import java.io.IOException;
import java.util.LinkedList;
@@ -47,8 +50,9 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
@Inject transient TextSecureMessageSenderFactory messageSenderFactory;
private final long messageId;
+ private final long filterRecipientId;
- public PushGroupSendJob(Context context, long messageId, String destination) {
+ public PushGroupSendJob(Context context, long messageId, String destination, long filterRecipientId) {
super(context, JobParameters.newBuilder()
.withPersistence()
.withGroupId(destination)
@@ -57,21 +61,25 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.withRetryCount(5)
.create());
- this.messageId = messageId;
+ this.messageId = messageId;
+ this.filterRecipientId = filterRecipientId;
}
@Override
public void onAdded() {
-
+ DatabaseFactory.getMmsDatabase(context)
+ .markAsSending(messageId);
}
@Override
- public void onSend(MasterSecret masterSecret) throws MmsException, IOException, NoSuchMessageException {
+ public void onSend(MasterSecret masterSecret)
+ throws MmsException, IOException, NoSuchMessageException, RecipientFormattingException
+ {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
try {
- deliver(masterSecret, message);
+ deliver(masterSecret, message, filterRecipientId);
database.markAsPush(messageId);
database.markAsSecure(messageId);
@@ -82,16 +90,27 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
notifyMediaMessageDeliveryFailed(context, messageId);
} catch (EncapsulatedExceptions e) {
Log.w(TAG, e);
- if (!e.getUnregisteredUserExceptions().isEmpty()) {
- database.markAsSentFailed(messageId);
+ List failures = new LinkedList<>();
+
+ for (NetworkFailureException nfe : e.getNetworkExceptions()) {
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, nfe.getE164number(), false).getPrimaryRecipient();
+ failures.add(new NetworkFailure(recipient.getRecipientId()));
}
+// for (UnregisteredUserException uue : e.getUnregisteredUserExceptions()) {
+// Recipient recipient = RecipientFactory.getRecipientsFromString(context, uue.getE164Number(), false).getPrimaryRecipient();
+// failures.add(new NetworkFailure(recipient.getRecipientId(), NetworkFailure.UNREGISTERED_FAILURE));
+// }
+
for (UntrustedIdentityException uie : e.getUntrustedIdentityExceptions()) {
- IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey());
- DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage);
- database.markAsSentFailed(messageId);
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false).getPrimaryRecipient();
+ database.addMismatchedIdentity(messageId, recipient.getRecipientId(), uie.getIdentityKey());
}
+ database.addFailures(messageId, failures);
+ database.markAsSentFailed(messageId);
+ database.markAsPush(messageId);
+
notifyMediaMessageDeliveryFailed(context, messageId);
}
}
@@ -107,14 +126,17 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
}
- private void deliver(MasterSecret masterSecret, SendReq message)
+ private void deliver(MasterSecret masterSecret, SendReq message, long filterRecipientId)
throws IOException, RecipientFormattingException, InvalidNumberException, EncapsulatedExceptions
{
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString());
Recipients recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
- List addresses = getPushAddresses(recipients);
List attachments = getAttachments(masterSecret, message);
+ List addresses;
+
+ if (filterRecipientId >= 0) addresses = getPushAddresses(filterRecipientId);
+ else addresses = getPushAddresses(recipients);
if (MmsSmsColumns.Types.isGroupUpdate(message.getDatabaseMessageBox()) ||
MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox()))
@@ -149,4 +171,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
return addresses;
}
+ private List getPushAddresses(long filterRecipientId) throws InvalidNumberException {
+ List addresses = new LinkedList<>();
+ addresses.add(getPushAddress(RecipientFactory.getRecipientForId(context, filterRecipientId, false)));
+ return addresses;
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
index 768f887eea..e83e76e45a 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
@@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
+import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.PartParser;
@@ -16,7 +17,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
@@ -55,12 +55,15 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
@Override
public void onAdded() {
-
+ MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
+ mmsDatabase.markAsSending(messageId);
+ mmsDatabase.markAsPush(messageId);
}
@Override
public void onSend(MasterSecret masterSecret)
- throws RetryLaterException, MmsException, NoSuchMessageException, UndeliverableMessageException
+ throws RetryLaterException, MmsException, NoSuchMessageException,
+ UndeliverableMessageException, RecipientFormattingException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
@@ -80,9 +83,13 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
database.markAsPendingSecureSmsFallback(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
} catch (UntrustedIdentityException uie) {
- IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey());
- DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage);
+ Log.w(TAG, uie);
+ Recipients recipients = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false);
+ long recipientId = recipients.getPrimaryRecipient().getRecipientId();
+
+ database.addMismatchedIdentity(messageId, recipientId, uie.getIdentityKey());
database.markAsSentFailed(messageId);
+ database.markAsPush(messageId);
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
index 3430168031..2092f0271a 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
@@ -9,12 +9,14 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
+import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
+import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
@@ -47,14 +49,16 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
@Override
public void onAdded() {
-
+ SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
+ smsDatabase.markAsSending(messageId);
+ smsDatabase.markAsPush(messageId);
}
@Override
- public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
- EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
- SmsMessageRecord record = database.getMessage(masterSecret, messageId);
- String destination = record.getIndividualRecipient().getNumber();
+ public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException, RecipientFormattingException {
+ EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
+ SmsMessageRecord record = database.getMessage(masterSecret, messageId);
+ String destination = record.getIndividualRecipient().getNumber();
try {
Log.w(TAG, "Sending message: " + messageId);
@@ -74,9 +78,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (UntrustedIdentityException e) {
Log.w(TAG, e);
- IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(e.getE164Number(), e.getIdentityKey());
- database.insertMessageInbox(masterSecret, identityUpdateMessage);
+ Recipients recipients = RecipientFactory.getRecipientsFromString(context, e.getE164Number(), false);
+ long recipientId = recipients.getPrimaryRecipient().getRecipientId();
+
+ database.addMismatchedIdentity(record.getId(), recipientId, e.getIdentityKey());
database.markAsSentFailed(record.getId());
+ database.markAsPush(record.getId());
}
}
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipients.java b/src/org/thoughtcrime/securesms/recipients/Recipients.java
index a7c6bfaf0a..b302f6ecf6 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipients.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipients.java
@@ -28,7 +28,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-public class Recipients {
+public class Recipients implements Iterable {
private List recipients;
@@ -165,4 +165,9 @@ public class Recipients {
public int describeContents() {
return 0;
}
+
+ @Override
+ public Iterator iterator() {
+ return recipients.iterator();
+ }
}
diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java
index 1ad0abc25a..5269ace9c6 100644
--- a/src/org/thoughtcrime/securesms/sms/MessageSender.java
+++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java
@@ -111,16 +111,24 @@ public class MessageSender {
}
}
+ public static void resendGroupMessage(Context context, MasterSecret masterSecret, MessageRecord messageRecord, long filterRecipientId) {
+ if (!messageRecord.isMms()) throw new AssertionError("Not Group");
+
+ Recipients recipients = DatabaseFactory.getMmsAddressDatabase(context).getRecipientsForId(messageRecord.getId());
+ sendGroupPush(context, recipients, messageRecord.getId(), filterRecipientId);
+ }
+
public static void resend(Context context, MasterSecret masterSecret, MessageRecord messageRecord) {
try {
- Recipients recipients = messageRecord.getRecipients();
long messageId = messageRecord.getId();
boolean forceSms = messageRecord.isForcedSms();
boolean keyExchange = messageRecord.isKeyExchange();
if (messageRecord.isMms()) {
+ Recipients recipients = DatabaseFactory.getMmsAddressDatabase(context).getRecipientsForId(messageId);
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);
} else {
+ Recipients recipients = messageRecord.getRecipients();
sendTextMessage(context, recipients, forceSms, keyExchange, messageId);
}
} catch (MmsException e) {
@@ -135,7 +143,7 @@ public class MessageSender {
if (!forceSms && isSelfSend(context, recipients)) {
sendMediaSelf(context, masterSecret, messageId);
} else if (isGroupPushSend(recipients)) {
- sendGroupPush(context, recipients, messageId);
+ sendGroupPush(context, recipients, messageId, -1);
} else if (!forceSms && isPushMediaSend(context, recipients)) {
sendMediaPush(context, recipients, messageId);
} else {
@@ -186,9 +194,9 @@ public class MessageSender {
jobManager.add(new PushMediaSendJob(context, messageId, recipients.getPrimaryRecipient().getNumber()));
}
- private static void sendGroupPush(Context context, Recipients recipients, long messageId) {
+ private static void sendGroupPush(Context context, Recipients recipients, long messageId, long filterRecipientId) {
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
- jobManager.add(new PushGroupSendJob(context, messageId, recipients.getPrimaryRecipient().getNumber()));
+ jobManager.add(new PushGroupSendJob(context, messageId, recipients.getPrimaryRecipient().getNumber(), filterRecipientId));
}
private static void sendSms(Context context, Recipients recipients, long messageId) {
diff --git a/src/org/thoughtcrime/securesms/util/DateUtils.java b/src/org/thoughtcrime/securesms/util/DateUtils.java
index 2a020610c1..0e3f7b4153 100644
--- a/src/org/thoughtcrime/securesms/util/DateUtils.java
+++ b/src/org/thoughtcrime/securesms/util/DateUtils.java
@@ -17,6 +17,10 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
+import android.text.format.DateFormat;
+
+import java.text.SimpleDateFormat;
+import java.util.Locale;
import org.thoughtcrime.securesms.R;
@@ -71,4 +75,17 @@ public class DateUtils extends android.text.format.DateUtils {
return DateUtils.formatDateTime(c, timestamp, formatFlags);
}
}
+
+ public static SimpleDateFormat getDetailedDateFormatter(Context context) {
+ String dateFormatPattern;
+
+ if (DateFormat.is24HourFormat(context)) {
+ dateFormatPattern = "MMM d, yyyy HH:mm:ss zzz";
+ } else {
+ dateFormatPattern = "MMM d, yyyy hh:mm:ssa zzz";
+ }
+
+ return new SimpleDateFormat(dateFormatPattern, Locale.getDefault());
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/util/Emoji.java b/src/org/thoughtcrime/securesms/util/Emoji.java
index 1f1017527c..bb65cc11b1 100644
--- a/src/org/thoughtcrime/securesms/util/Emoji.java
+++ b/src/org/thoughtcrime/securesms/util/Emoji.java
@@ -19,14 +19,12 @@ import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
-import com.google.thoughtcrimegson.Gson;
-import com.google.thoughtcrimegson.reflect.TypeToken;
+import com.fasterxml.jackson.databind.type.TypeFactory;
import org.thoughtcrime.securesms.R;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.concurrent.ExecutorService;
@@ -35,6 +33,8 @@ import java.util.regex.Pattern;
public class Emoji {
+ private static final String TAG = Emoji.class.getSimpleName();
+
private static ExecutorService executor = Util.newSingleThreadedLifoExecutor();
public static final int[][] PAGES = {
@@ -303,9 +303,16 @@ public class Emoji {
}
String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]");
- Type type = new TypeToken>() {
- }.getType();
- recentlyUsed = new Gson().fromJson(serialized, type);
+
+ try {
+ recentlyUsed = JsonUtils.getMapper().readValue(serialized, TypeFactory.defaultInstance()
+ .constructCollectionType(LinkedHashSet.class, String.class));
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ recentlyUsed = new LinkedHashSet<>();
+ }
+
+ recentlyUsed = new LinkedHashSet<>();
}
public static String[] getRecentlyUsed(Context context) {
@@ -337,10 +344,15 @@ public class Emoji {
@Override
protected Void doInBackground(Void... params) {
- String serialized = new Gson().toJson(latestRecentlyUsed);
- prefs.edit()
- .putString(EMOJI_LRU_PREFERENCE, serialized)
- .apply();
+ try {
+ String serialized = JsonUtils.toJson(latestRecentlyUsed);
+ prefs.edit()
+ .putString(EMOJI_LRU_PREFERENCE, serialized)
+ .apply();
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+
return null;
}
}.execute();
diff --git a/src/org/thoughtcrime/securesms/util/JsonUtils.java b/src/org/thoughtcrime/securesms/util/JsonUtils.java
new file mode 100644
index 0000000000..92862d647b
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/JsonUtils.java
@@ -0,0 +1,33 @@
+package org.thoughtcrime.securesms.util;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class JsonUtils {
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ static {
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ public static T fromJson(String serialized, Class clazz) throws IOException {
+ return objectMapper.readValue(serialized, clazz);
+ }
+
+ public static T fromJson(InputStreamReader serialized, Class clazz) throws IOException {
+ return objectMapper.readValue(serialized, clazz);
+ }
+
+ public static String toJson(Object object) throws IOException {
+ return objectMapper.writeValueAsString(object);
+ }
+
+ public static ObjectMapper getMapper() {
+ return objectMapper;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/util/RecipientViewUtil.java b/src/org/thoughtcrime/securesms/util/RecipientViewUtil.java
new file mode 100644
index 0000000000..3660a8ed08
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/RecipientViewUtil.java
@@ -0,0 +1,83 @@
+package org.thoughtcrime.securesms.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.provider.Contacts.Intents;
+import android.provider.ContactsContract.QuickContact;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.view.View;
+import android.widget.ImageView;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
+
+public class RecipientViewUtil {
+ public static CharSequence formatFrom(Context context, Recipient recipient) {
+ return formatFrom(context, new Recipients(recipient));
+ }
+
+ public static CharSequence formatFrom(Context context, Recipients from) {
+ return formatFrom(context, from, true);
+ }
+
+ public static CharSequence formatFrom(Context context, Recipients from, boolean read) {
+ int attributes[] = new int[] {R.attr.conversation_list_item_count_color};
+ TypedArray colors = context.obtainStyledAttributes(attributes);
+
+ final String fromString;
+ final boolean isUnnamedGroup = from.isGroupRecipient() && TextUtils.isEmpty(from.getPrimaryRecipient().getName());
+ if (isUnnamedGroup) {
+ fromString = context.getString(R.string.ConversationActivity_unnamed_group);
+ } else {
+ fromString = from.toShortString();
+ }
+ SpannableStringBuilder builder = new SpannableStringBuilder(fromString);
+
+ final int typeface;
+ if (isUnnamedGroup) {
+ if (!read) typeface = Typeface.BOLD_ITALIC;
+ else typeface = Typeface.ITALIC;
+ } else if (!read) {
+ typeface = Typeface.BOLD;
+ } else {
+ typeface = Typeface.NORMAL;
+ }
+
+ builder.setSpan(new StyleSpan(typeface), 0, builder.length(),
+ Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+
+
+ colors.recycle();
+ return builder;
+ }
+
+ public static void setContactPhoto(final Context context, final ImageView imageView, final Recipient recipient, boolean showQuickContact) {
+ if (recipient == null) return;
+
+ imageView.setImageBitmap(recipient.getContactPhoto());
+
+ if (!recipient.isGroupRecipient() && showQuickContact) {
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (recipient.getContactUri() != null) {
+ QuickContact.showQuickContact(context, imageView, recipient.getContactUri(), QuickContact.MODE_LARGE, null);
+ } else {
+ Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null));
+ context.startActivity(intent);
+ }
+ }
+ });
+ } else {
+ imageView.setOnClickListener(null);
+ }
+ }
+
+}
diff --git a/strip_play_services.gradle b/strip_play_services.gradle
deleted file mode 100644
index 03c9b73188..0000000000
--- a/strip_play_services.gradle
+++ /dev/null
@@ -1,109 +0,0 @@
-// adapted from https://gist.github.com/CedricGatay/4e21ce855bd2562f9257
-// which was adapted from https://gist.github.com/dmarcato/d7c91b94214acd936e42
-
-def toCamelCase(String string) {
- String result = ""
- string.findAll("[^\\W]+") { String word ->
- result += word.capitalize()
- }
- return result
-}
-
-afterEvaluate { project ->
- Configuration runtimeConfiguration = project.configurations.getByName('compile')
- println runtimeConfiguration
- ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult
- // Forces resolve of configuration
- ModuleVersionIdentifier module = resolution.getAllComponents().find {
- it.moduleVersion.name.equals("play-services")
- }.moduleVersion
-
-
- def playServicesLibName = toCamelCase("${module.group} ${module.name} ${module.version}")
- String prepareTaskName = "prepare${playServicesLibName}Library"
- File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir
-
-
- def tmpDir = new File(project.buildDir, 'intermediates/tmp')
- tmpDir.mkdirs()
- def libFile = new File(tmpDir, "${playServicesLibName}.marker")
-
- def strippedClassFileName = "${playServicesLibName}.jar"
- def classesStrippedJar = new File(tmpDir, strippedClassFileName)
-
- def packageToExclude = [
- "com/google/ads/**",
- //"com/google/android/gms/actions/**",
- "com/google/android/gms/ads/**",
- "com/google/android/gms/analytics/**",
- //"com/google/android/gms/appindexing/**",
- //"com/google/android/gms/appstate/**",
- //"com/google/android/gms/auth/**",
- //"com/google/android/gms/cast/**",
- "com/google/android/gms/drive/**",
- "com/google/android/gms/fitness/**",
- "com/google/android/gms/games/**",
- //"com/google/android/gms/gcm/**",
- //"com/google/android/gms/identity/**",
- "com/google/android/gms/location/**",
- "com/google/android/gms/maps/**",
- "com/google/android/gms/panorama/**",
- "com/google/android/gms/plus/**",
- //"com/google/android/gms/security/**",
- //"com/google/android/gms/tagmanager/**",
- "com/google/android/gms/wallet/**",
- "com/google/android/gms/wearable/**"
- ]
-
- Task stripPlayServices = project.tasks.create(name: 'stripPlayServices', group: "Strip") {
- inputs.files new File(playServiceRootFolder, "classes.jar")
- outputs.dir playServiceRootFolder
- description 'Strip useless packages from Google Play Services library to avoid reaching dex limit'
-
- doLast {
- def packageExcludesAsString = packageToExclude.join(",")
- if (libFile.exists()
- && libFile.text == packageExcludesAsString
- && classesStrippedJar.exists()){
- println "Play services already stripped"
- copy {
- from(file(classesStrippedJar))
- into(file(playServiceRootFolder))
- rename { fileName ->
- fileName = "classes.jar"
- }
- }
- }else {
- copy {
- from(file(new File(playServiceRootFolder, "classes.jar")))
- into(file(playServiceRootFolder))
- rename { fileName ->
- fileName = "classes_orig.jar"
- }
- }
- tasks.create(name: "stripPlayServices" + module.version, type: Jar) {
- destinationDir = playServiceRootFolder
- archiveName = "classes.jar"
- from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) {
- exclude packageToExclude
- }
- }.execute()
- delete file(new File(playServiceRootFolder, "classes_orig.jar"))
- copy {
- from(file(new File(playServiceRootFolder, "classes.jar")))
- into(file(tmpDir))
- rename { fileName ->
- fileName = strippedClassFileName
- }
- }
- libFile.text = packageExcludesAsString
- }
- }
- }
-
- project.tasks.findAll {
- it.name.startsWith('prepare') && it.name.endsWith('Dependencies')
- }.each { Task task ->
- task.dependsOn stripPlayServices
- }
-}
\ No newline at end of file