Better UX handling on identity key mismatches.
1) Migrate from GSON to Jackson everywhere. 2) Add support for storing identity key conflicts on message rows. 3) Add limited support for surfacing identity key conflicts in UI.
This commit is contained in:
parent
4397b55ceb
commit
00d7b5c284
76 changed files with 2395 additions and 721 deletions
|
@ -125,10 +125,16 @@
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".GroupCreateActivity"
|
<activity android:name=".MessageDetailsActivity"
|
||||||
android:windowSoftInputMode="stateVisible"
|
android:label="Message Details"
|
||||||
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
|
android:launchMode="singleTask"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".GroupCreateActivity"
|
||||||
|
android:windowSoftInputMode="stateVisible"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".DatabaseMigrationActivity"
|
<activity android:name=".DatabaseMigrationActivity"
|
||||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
|
|
42
build.gradle
42
build.gradle
|
@ -20,9 +20,6 @@ repositories {
|
||||||
maven {
|
maven {
|
||||||
url "https://raw.github.com/whispersystems/maven/master/preferencefragment/releases/"
|
url "https://raw.github.com/whispersystems/maven/master/preferencefragment/releases/"
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
url "https://raw.github.com/whispersystems/maven/master/gson/releases/"
|
|
||||||
}
|
|
||||||
maven {
|
maven {
|
||||||
url "https://raw.github.com/whispersystems/maven/master/smil/releases/"
|
url "https://raw.github.com/whispersystems/maven/master/smil/releases/"
|
||||||
}
|
}
|
||||||
|
@ -77,40 +74,6 @@ dependencies {
|
||||||
compile project(':libtextsecure')
|
compile project(':libtextsecure')
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyVerification {
|
|
||||||
verify = [
|
|
||||||
'me.leolin:ShortcutBadger:027977c718035e5997035e04e05152d6c72d94df645e8b7099a274ada722bd14',
|
|
||||||
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
|
|
||||||
'com.google.android.gms:play-services-base:832cb6b3130e871db6a412c4ab585656dbcc5e7948101f190186757785703f75',
|
|
||||||
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
|
|
||||||
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
|
|
||||||
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
|
|
||||||
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
|
|
||||||
'com.makeramen:roundedimageview:7dda2e78c406760e5c356ccce59b0df46b5b171cf18abb891998594405021548',
|
|
||||||
'com.afollestad:material-dialogs:ccb013e6572c86cfcca433855cf0dbfbff9b5e7bb9d1f504b761a6bc6f467b60',
|
|
||||||
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
|
||||||
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
|
|
||||||
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
|
|
||||||
'com.melnykov:floatingactionbutton:0679ad9f7d61eb7aeab91e8dc56358cdedd5b1c1b9c48464499ffa05c40d3985',
|
|
||||||
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
|
||||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
|
||||||
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
|
|
||||||
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
|
|
||||||
'org.whispersystems:jobmanager:01f35586c43aa3806f1c18d3d6a5a972def98103ba1a5a9ca3eec08d15f974b7',
|
|
||||||
'org.whispersystems:libpastelog:3ccf00fe1597eb8ca1e5de99b17fc225387a1b80b5bbc00ec1bc4d4f3ea9cdde',
|
|
||||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
|
||||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
|
||||||
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
|
||||||
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
|
||||||
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
|
|
||||||
'org.whispersystems:axolotl-android:7617256d05aaecd7b5475cd55e42773d7079167a22ca48512bcb0f84f8473cc9',
|
|
||||||
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
|
|
||||||
'com.android.support:support-annotations:fdee2354787ef66b268e75958de3f7f6c4f8f325510a6dac9f49c929f83a63de',
|
|
||||||
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
|
||||||
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 21
|
compileSdkVersion 21
|
||||||
buildToolsVersion '21.1.2'
|
buildToolsVersion '21.1.2'
|
||||||
|
@ -173,6 +136,11 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/LICENSE'
|
||||||
|
exclude 'META-INF/NOTICE'
|
||||||
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
abortOnError false
|
abortOnError false
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,13 @@ apply plugin: 'maven'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
|
||||||
url "https://raw.github.com/whispersystems/maven/master/gson/releases/"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.google.protobuf:protobuf-java:2.5.0'
|
compile 'com.google.protobuf:protobuf-java:2.5.0'
|
||||||
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
|
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
|
||||||
compile 'org.whispersystems:gson:2.2.4'
|
compile 'com.fasterxml.jackson.core:jackson-databind:2.5.0'
|
||||||
|
|
||||||
compile 'org.whispersystems:axolotl-android:1.0.0'
|
compile 'org.whispersystems:axolotl-android:1.0.0'
|
||||||
compile 'com.squareup.okhttp:okhttp:2.2.0'
|
compile 'com.squareup.okhttp:okhttp:2.2.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
||||||
import org.whispersystems.textsecure.api.push.PushAddress;
|
import org.whispersystems.textsecure.api.push.PushAddress;
|
||||||
import org.whispersystems.textsecure.api.push.TrustStore;
|
import org.whispersystems.textsecure.api.push.TrustStore;
|
||||||
|
import org.whispersystems.textsecure.api.push.exceptions.NetworkFailureException;
|
||||||
|
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
|
||||||
import org.whispersystems.textsecure.internal.push.MismatchedDevices;
|
import org.whispersystems.textsecure.internal.push.MismatchedDevices;
|
||||||
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
|
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
|
||||||
import org.whispersystems.textsecure.internal.push.OutgoingPushMessageList;
|
import org.whispersystems.textsecure.internal.push.OutgoingPushMessageList;
|
||||||
|
@ -176,6 +178,7 @@ public class TextSecureMessageSender {
|
||||||
{
|
{
|
||||||
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
|
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
|
||||||
List<UnregisteredUserException> unregisteredUsers = new LinkedList<>();
|
List<UnregisteredUserException> unregisteredUsers = new LinkedList<>();
|
||||||
|
List<NetworkFailureException> networkExceptions = new LinkedList<>();
|
||||||
|
|
||||||
for (PushAddress recipient : recipients) {
|
for (PushAddress recipient : recipients) {
|
||||||
try {
|
try {
|
||||||
|
@ -186,11 +189,14 @@ public class TextSecureMessageSender {
|
||||||
} catch (UnregisteredUserException e) {
|
} catch (UnregisteredUserException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
unregisteredUsers.add(e);
|
unregisteredUsers.add(e);
|
||||||
|
} catch (PushNetworkException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
networkExceptions.add(new NetworkFailureException(recipient.getNumber(), e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty()) {
|
if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) {
|
||||||
throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers);
|
throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers, networkExceptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,24 +16,28 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.api.push;
|
package org.whispersystems.textsecure.api.push;
|
||||||
|
|
||||||
import com.google.thoughtcrimegson.GsonBuilder;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.thoughtcrimegson.JsonDeserializationContext;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.google.thoughtcrimegson.JsonDeserializer;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.google.thoughtcrimegson.JsonElement;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.google.thoughtcrimegson.JsonParseException;
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.google.thoughtcrimegson.JsonPrimitive;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.google.thoughtcrimegson.JsonSerializationContext;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.google.thoughtcrimegson.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.libaxolotl.ecc.ECPublicKey;
|
||||||
import org.whispersystems.textsecure.internal.util.Base64;
|
|
||||||
import org.whispersystems.textsecure.internal.push.PreKeyEntity;
|
import org.whispersystems.textsecure.internal.push.PreKeyEntity;
|
||||||
|
import org.whispersystems.textsecure.internal.util.Base64;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
|
|
||||||
public class SignedPreKeyEntity extends PreKeyEntity {
|
public class SignedPreKeyEntity extends PreKeyEntity {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@JsonSerialize(using = ByteArraySerializer.class)
|
||||||
|
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||||
private byte[] signature;
|
private byte[] signature;
|
||||||
|
|
||||||
public SignedPreKeyEntity() {}
|
public SignedPreKeyEntity() {}
|
||||||
|
@ -47,42 +51,18 @@ public class SignedPreKeyEntity extends PreKeyEntity {
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String toJson(SignedPreKeyEntity entity) {
|
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
|
||||||
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<byte[]>, JsonDeserializer<byte[]>
|
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(byte[] signature, Type type,
|
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
JsonSerializationContext jsonSerializationContext)
|
gen.writeString(Base64.encodeBytesWithoutPadding(value));
|
||||||
{
|
|
||||||
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(signature));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] deserialize(JsonElement jsonElement, Type type,
|
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
JsonDeserializationContext jsonDeserializationContext)
|
return Base64.decodeWithoutPadding(p.getValueAsString());
|
||||||
throws JsonParseException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new JsonParseException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,15 @@ public class EncapsulatedExceptions extends Throwable {
|
||||||
|
|
||||||
private final List<UntrustedIdentityException> untrustedIdentityExceptions;
|
private final List<UntrustedIdentityException> untrustedIdentityExceptions;
|
||||||
private final List<UnregisteredUserException> unregisteredUserExceptions;
|
private final List<UnregisteredUserException> unregisteredUserExceptions;
|
||||||
|
private final List<NetworkFailureException> networkExceptions;
|
||||||
|
|
||||||
public EncapsulatedExceptions(List<UntrustedIdentityException> untrustedIdentities,
|
public EncapsulatedExceptions(List<UntrustedIdentityException> untrustedIdentities,
|
||||||
List<UnregisteredUserException> unregisteredUsers)
|
List<UnregisteredUserException> unregisteredUsers,
|
||||||
|
List<NetworkFailureException> networkExceptions)
|
||||||
{
|
{
|
||||||
this.untrustedIdentityExceptions = untrustedIdentities;
|
this.untrustedIdentityExceptions = untrustedIdentities;
|
||||||
this.unregisteredUserExceptions = unregisteredUsers;
|
this.unregisteredUserExceptions = unregisteredUsers;
|
||||||
|
this.networkExceptions = networkExceptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UntrustedIdentityException> getUntrustedIdentityExceptions() {
|
public List<UntrustedIdentityException> getUntrustedIdentityExceptions() {
|
||||||
|
@ -39,4 +42,8 @@ public class EncapsulatedExceptions extends Throwable {
|
||||||
public List<UnregisteredUserException> getUnregisteredUserExceptions() {
|
public List<UnregisteredUserException> getUnregisteredUserExceptions() {
|
||||||
return unregisteredUserExceptions;
|
return unregisteredUserExceptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<NetworkFailureException> getNetworkExceptions() {
|
||||||
|
return networkExceptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.whispersystems.textsecure.api.push.exceptions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class PushNetworkException extends IOException {
|
public class PushNetworkException extends IOException {
|
||||||
|
|
||||||
public PushNetworkException(Exception exception) {
|
public PushNetworkException(Exception exception) {
|
||||||
super(exception);
|
super(exception);
|
||||||
}
|
}
|
||||||
|
@ -26,4 +27,5 @@ public class PushNetworkException extends IOException {
|
||||||
public PushNetworkException(String s) {
|
public PushNetworkException(String s) {
|
||||||
super(s);
|
super(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,17 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
public class AccountAttributes {
|
public class AccountAttributes {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private String signalingKey;
|
private String signalingKey;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private boolean supportsSms;
|
private boolean supportsSms;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private int registrationId;
|
private int registrationId;
|
||||||
|
|
||||||
public AccountAttributes(String signalingKey, boolean supportsSms, int registrationId) {
|
public AccountAttributes(String signalingKey, boolean supportsSms, int registrationId) {
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
public class DeviceCode {
|
public class DeviceCode {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private String verificationCode;
|
private String verificationCode;
|
||||||
|
|
||||||
public String getVerificationCode() {
|
public String getVerificationCode() {
|
||||||
|
|
|
@ -16,11 +16,15 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MismatchedDevices {
|
public class MismatchedDevices {
|
||||||
|
@JsonProperty
|
||||||
private List<Integer> missingDevices;
|
private List<Integer> missingDevices;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private List<Integer> extraDevices;
|
private List<Integer> extraDevices;
|
||||||
|
|
||||||
public List<Integer> getMissingDevices() {
|
public List<Integer> getMissingDevices() {
|
||||||
|
|
|
@ -17,14 +17,20 @@
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import org.whispersystems.textsecure.api.push.PushAddress;
|
import org.whispersystems.textsecure.api.push.PushAddress;
|
||||||
import org.whispersystems.textsecure.internal.util.Base64;
|
import org.whispersystems.textsecure.internal.util.Base64;
|
||||||
|
|
||||||
public class OutgoingPushMessage {
|
public class OutgoingPushMessage {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private int type;
|
private int type;
|
||||||
|
@JsonProperty
|
||||||
private int destinationDeviceId;
|
private int destinationDeviceId;
|
||||||
|
@JsonProperty
|
||||||
private int destinationRegistrationId;
|
private int destinationRegistrationId;
|
||||||
|
@JsonProperty
|
||||||
private String body;
|
private String body;
|
||||||
|
|
||||||
public OutgoingPushMessage(PushAddress address, int deviceId, PushBody body) {
|
public OutgoingPushMessage(PushAddress address, int deviceId, PushBody body) {
|
||||||
|
|
|
@ -16,16 +16,22 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class OutgoingPushMessageList {
|
public class OutgoingPushMessageList {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private String destination;
|
private String destination;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private String relay;
|
private String relay;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private long timestamp;
|
private long timestamp;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private List<OutgoingPushMessage> messages;
|
private List<OutgoingPushMessage> messages;
|
||||||
|
|
||||||
public OutgoingPushMessageList(String destination, long timestamp, String relay,
|
public OutgoingPushMessageList(String destination, long timestamp, String relay,
|
||||||
|
|
|
@ -16,14 +16,15 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
import com.google.thoughtcrimegson.GsonBuilder;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.thoughtcrimegson.JsonDeserializationContext;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.google.thoughtcrimegson.JsonDeserializer;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.google.thoughtcrimegson.JsonElement;
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.google.thoughtcrimegson.JsonParseException;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.google.thoughtcrimegson.JsonPrimitive;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.google.thoughtcrimegson.JsonSerializationContext;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
import com.google.thoughtcrimegson.JsonSerializer;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||||
|
@ -31,11 +32,15 @@ import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||||
import org.whispersystems.textsecure.internal.util.Base64;
|
import org.whispersystems.textsecure.internal.util.Base64;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
|
|
||||||
public class PreKeyEntity {
|
public class PreKeyEntity {
|
||||||
|
|
||||||
private int keyId;
|
@JsonProperty
|
||||||
|
private int keyId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@JsonSerialize(using = ECPublicKeySerializer.class)
|
||||||
|
@JsonDeserialize(using = ECPublicKeyDeserializer.class)
|
||||||
private ECPublicKey publicKey;
|
private ECPublicKey publicKey;
|
||||||
|
|
||||||
public PreKeyEntity() {}
|
public PreKeyEntity() {}
|
||||||
|
@ -53,32 +58,21 @@ public class PreKeyEntity {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GsonBuilder forBuilder(GsonBuilder builder) {
|
private static class ECPublicKeySerializer extends JsonSerializer<ECPublicKey> {
|
||||||
return builder.registerTypeAdapter(ECPublicKey.class, new ECPublicKeyJsonAdapter());
|
@Override
|
||||||
|
public void serialize(ECPublicKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ECPublicKeyDeserializer extends JsonDeserializer<ECPublicKey> {
|
||||||
private static class ECPublicKeyJsonAdapter
|
|
||||||
implements JsonSerializer<ECPublicKey>, JsonDeserializer<ECPublicKey>
|
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(ECPublicKey preKeyPublic, Type type,
|
public ECPublicKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
JsonSerializationContext jsonSerializationContext)
|
|
||||||
{
|
|
||||||
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(preKeyPublic.serialize()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ECPublicKey deserialize(JsonElement jsonElement, Type type,
|
|
||||||
JsonDeserializationContext jsonDeserializationContext)
|
|
||||||
throws JsonParseException
|
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
return Curve.decodePoint(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
|
return Curve.decodePoint(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
|
||||||
} catch (InvalidKeyException | IOException e) {
|
} catch (InvalidKeyException e) {
|
||||||
throw new JsonParseException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,26 +16,32 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
import com.google.thoughtcrimegson.GsonBuilder;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.thoughtcrimegson.JsonDeserializationContext;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.google.thoughtcrimegson.JsonDeserializer;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.google.thoughtcrimegson.JsonElement;
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.google.thoughtcrimegson.JsonParseException;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.google.thoughtcrimegson.JsonPrimitive;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.google.thoughtcrimegson.JsonSerializationContext;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
import com.google.thoughtcrimegson.JsonSerializer;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
|
||||||
import org.whispersystems.libaxolotl.IdentityKey;
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
import org.whispersystems.textsecure.internal.util.Base64;
|
import org.whispersystems.textsecure.internal.util.Base64;
|
||||||
|
import org.whispersystems.textsecure.internal.util.JsonUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PreKeyResponse {
|
public class PreKeyResponse {
|
||||||
|
|
||||||
private IdentityKey identityKey;
|
@JsonProperty
|
||||||
|
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
|
||||||
|
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
|
||||||
|
private IdentityKey identityKey;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private List<PreKeyResponseItem> devices;
|
private List<PreKeyResponseItem> devices;
|
||||||
|
|
||||||
public IdentityKey getIdentityKey() {
|
public IdentityKey getIdentityKey() {
|
||||||
|
@ -46,36 +52,5 @@ public class PreKeyResponse {
|
||||||
return devices;
|
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<IdentityKey>, JsonDeserializer<IdentityKey>
|
|
||||||
{
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,22 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
import com.google.thoughtcrimegson.GsonBuilder;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
|
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
|
||||||
|
|
||||||
public class PreKeyResponseItem {
|
public class PreKeyResponseItem {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private int deviceId;
|
private int deviceId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private int registrationId;
|
private int registrationId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private SignedPreKeyEntity signedPreKey;
|
private SignedPreKeyEntity signedPreKey;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private PreKeyEntity preKey;
|
private PreKeyEntity preKey;
|
||||||
|
|
||||||
public int getDeviceId() {
|
public int getDeviceId() {
|
||||||
|
@ -43,7 +50,4 @@ public class PreKeyResponseItem {
|
||||||
return preKey;
|
return preKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GsonBuilder forBuilder(GsonBuilder builder) {
|
|
||||||
return SignedPreKeyEntity.forBuilder(builder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,29 @@
|
||||||
package org.whispersystems.textsecure.internal.push;
|
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.libaxolotl.IdentityKey;
|
||||||
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
|
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
|
||||||
|
import org.whispersystems.textsecure.internal.util.JsonUtil;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PreKeyState {
|
public class PreKeyState {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
|
||||||
|
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
|
||||||
private IdentityKey identityKey;
|
private IdentityKey identityKey;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private List<PreKeyEntity> preKeys;
|
private List<PreKeyEntity> preKeys;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private PreKeyEntity lastResortKey;
|
private PreKeyEntity lastResortKey;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private SignedPreKeyEntity signedPreKey;
|
private SignedPreKeyEntity signedPreKey;
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,10 +36,4 @@ public class PreKeyState {
|
||||||
this.identityKey = identityKey;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
public class PreKeyStatus {
|
public class PreKeyStatus {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private int count;
|
private int count;
|
||||||
|
|
||||||
public PreKeyStatus() {}
|
public PreKeyStatus() {}
|
||||||
|
|
|
@ -18,8 +18,7 @@ package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.thoughtcrimegson.Gson;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.thoughtcrimegson.JsonParseException;
|
|
||||||
|
|
||||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
import org.whispersystems.libaxolotl.IdentityKey;
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
|
@ -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.push.exceptions.StaleDevicesException;
|
||||||
import org.whispersystems.textsecure.internal.util.Base64;
|
import org.whispersystems.textsecure.internal.util.Base64;
|
||||||
import org.whispersystems.textsecure.internal.util.BlacklistingTrustManager;
|
import org.whispersystems.textsecure.internal.util.BlacklistingTrustManager;
|
||||||
|
import org.whispersystems.textsecure.internal.util.JsonUtil;
|
||||||
import org.whispersystems.textsecure.internal.util.Util;
|
import org.whispersystems.textsecure.internal.util.Util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -115,17 +115,17 @@ public class PushServiceSocket {
|
||||||
{
|
{
|
||||||
AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, supportsSms, registrationId);
|
AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, supportsSms, registrationId);
|
||||||
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode),
|
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode),
|
||||||
"PUT", new Gson().toJson(signalingKeyEntity));
|
"PUT", JsonUtil.toJson(signalingKeyEntity));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNewDeviceVerificationCode() throws IOException {
|
public String getNewDeviceVerificationCode() throws IOException {
|
||||||
String responseText = makeRequest(PROVISIONING_CODE_PATH, "GET", null);
|
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 {
|
public void sendProvisioningMessage(String destination, byte[] body) throws IOException {
|
||||||
makeRequest(String.format(PROVISIONING_MESSAGE_PATH, destination), "PUT",
|
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 {
|
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 {
|
public void registerGcmId(String gcmRegistrationId) throws IOException {
|
||||||
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId, true);
|
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 {
|
public void unregisterGcmId() throws IOException {
|
||||||
|
@ -151,11 +151,10 @@ public class PushServiceSocket {
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
try {
|
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);
|
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) {
|
} catch (NotFoundException nfe) {
|
||||||
throw new UnregisteredUserException(bundle.getDestination(), nfe);
|
throw new UnregisteredUserException(bundle.getDestination(), nfe);
|
||||||
}
|
}
|
||||||
|
@ -184,13 +183,13 @@ public class PushServiceSocket {
|
||||||
signedPreKey.getSignature());
|
signedPreKey.getSignature());
|
||||||
|
|
||||||
makeRequest(String.format(PREKEY_PATH, ""), "PUT",
|
makeRequest(String.format(PREKEY_PATH, ""), "PUT",
|
||||||
PreKeyState.toJson(new PreKeyState(entities, lastResortEntity,
|
JsonUtil.toJson(new PreKeyState(entities, lastResortEntity,
|
||||||
signedPreKeyEntity, identityKey)));
|
signedPreKeyEntity, identityKey)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAvailablePreKeys() throws IOException {
|
public int getAvailablePreKeys() throws IOException {
|
||||||
String responseText = makeRequest(PREKEY_METADATA_PATH, "GET", null);
|
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();
|
return preKeyStatus.getCount();
|
||||||
}
|
}
|
||||||
|
@ -209,7 +208,7 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
String responseText = makeRequest(path, "GET", null);
|
String responseText = makeRequest(path, "GET", null);
|
||||||
PreKeyResponse response = PreKeyResponse.fromJson(responseText);
|
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
|
||||||
List<PreKeyBundle> bundles = new LinkedList<>();
|
List<PreKeyBundle> bundles = new LinkedList<>();
|
||||||
|
|
||||||
for (PreKeyResponseItem device : response.getDevices()) {
|
for (PreKeyResponseItem device : response.getDevices()) {
|
||||||
|
@ -236,7 +235,7 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
return bundles;
|
return bundles;
|
||||||
} catch (JsonParseException e) {
|
} catch (JsonUtil.JsonParseException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
} catch (NotFoundException nfe) {
|
} catch (NotFoundException nfe) {
|
||||||
throw new UnregisteredUserException(destination.getNumber(), nfe);
|
throw new UnregisteredUserException(destination.getNumber(), nfe);
|
||||||
|
@ -253,7 +252,7 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
String responseText = makeRequest(path, "GET", null);
|
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)
|
if (response.getDevices() == null || response.getDevices().size() < 1)
|
||||||
throw new IOException("Empty prekey list");
|
throw new IOException("Empty prekey list");
|
||||||
|
@ -278,7 +277,7 @@ public class PushServiceSocket {
|
||||||
|
|
||||||
return new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId, preKey,
|
return new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId, preKey,
|
||||||
signedPreKeyId, signedPreKey, signedPreKeySignature, response.getIdentityKey());
|
signedPreKeyId, signedPreKey, signedPreKeySignature, response.getIdentityKey());
|
||||||
} catch (JsonParseException e) {
|
} catch (JsonUtil.JsonParseException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
} catch (NotFoundException nfe) {
|
} catch (NotFoundException nfe) {
|
||||||
throw new UnregisteredUserException(destination.getNumber(), nfe);
|
throw new UnregisteredUserException(destination.getNumber(), nfe);
|
||||||
|
@ -288,7 +287,7 @@ public class PushServiceSocket {
|
||||||
public SignedPreKeyEntity getCurrentSignedPreKey() throws IOException {
|
public SignedPreKeyEntity getCurrentSignedPreKey() throws IOException {
|
||||||
try {
|
try {
|
||||||
String responseText = makeRequest(SIGNED_PREKEY_PATH, "GET", null);
|
String responseText = makeRequest(SIGNED_PREKEY_PATH, "GET", null);
|
||||||
return SignedPreKeyEntity.fromJson(responseText);
|
return JsonUtil.fromJson(responseText, SignedPreKeyEntity.class);
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
Log.w("PushServiceSocket", e);
|
Log.w("PushServiceSocket", e);
|
||||||
return null;
|
return null;
|
||||||
|
@ -299,12 +298,12 @@ public class PushServiceSocket {
|
||||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||||
signedPreKey.getKeyPair().getPublicKey(),
|
signedPreKey.getKeyPair().getPublicKey(),
|
||||||
signedPreKey.getSignature());
|
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 {
|
public long sendAttachment(PushAttachmentData attachment) throws IOException {
|
||||||
String response = makeRequest(String.format(ATTACHMENT_PATH, ""), "GET", null);
|
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) {
|
if (attachmentKey == null || attachmentKey.getLocation() == null) {
|
||||||
throw new IOException("Server failed to allocate an attachment key!");
|
throw new IOException("Server failed to allocate an attachment key!");
|
||||||
|
@ -326,7 +325,7 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
String response = makeRequest(path, "GET", null);
|
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());
|
Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + descriptor.getLocation());
|
||||||
|
|
||||||
|
@ -337,8 +336,8 @@ public class PushServiceSocket {
|
||||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||||
{
|
{
|
||||||
ContactTokenList contactTokenList = new ContactTokenList(new LinkedList<>(contactTokens));
|
ContactTokenList contactTokenList = new ContactTokenList(new LinkedList<>(contactTokens));
|
||||||
String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", new Gson().toJson(contactTokenList));
|
String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", JsonUtil.toJson(contactTokenList));
|
||||||
ContactTokenDetailsList activeTokens = new Gson().fromJson(response, ContactTokenDetailsList.class);
|
ContactTokenDetailsList activeTokens = JsonUtil.fromJson(response, ContactTokenDetailsList.class);
|
||||||
|
|
||||||
return activeTokens.getContacts();
|
return activeTokens.getContacts();
|
||||||
}
|
}
|
||||||
|
@ -346,7 +345,7 @@ public class PushServiceSocket {
|
||||||
public ContactTokenDetails getContactTokenDetails(String contactToken) throws IOException {
|
public ContactTokenDetails getContactTokenDetails(String contactToken) throws IOException {
|
||||||
try {
|
try {
|
||||||
String response = makeRequest(String.format(DIRECTORY_VERIFY_PATH, contactToken), "GET", null);
|
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) {
|
} catch (NotFoundException nfe) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -463,14 +462,14 @@ public class PushServiceSocket {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PushNetworkException(e);
|
throw new PushNetworkException(e);
|
||||||
}
|
}
|
||||||
throw new MismatchedDevicesException(new Gson().fromJson(response, MismatchedDevices.class));
|
throw new MismatchedDevicesException(JsonUtil.fromJson(response, MismatchedDevices.class));
|
||||||
case 410:
|
case 410:
|
||||||
try {
|
try {
|
||||||
response = Util.readFully(connection.getErrorStream());
|
response = Util.readFully(connection.getErrorStream());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PushNetworkException(e);
|
throw new PushNetworkException(e);
|
||||||
}
|
}
|
||||||
throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.class));
|
throw new StaleDevicesException(JsonUtil.fromJson(response, StaleDevices.class));
|
||||||
case 417:
|
case 417:
|
||||||
throw new ExpectationFailedException();
|
throw new ExpectationFailedException();
|
||||||
}
|
}
|
||||||
|
@ -524,9 +523,7 @@ public class PushServiceSocket {
|
||||||
return connection;
|
return connection;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PushNetworkException(e);
|
throw new PushNetworkException(e);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (KeyManagementException e) {
|
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -540,7 +537,11 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class GcmRegistrationId {
|
private static class GcmRegistrationId {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private String gcmRegistrationId;
|
private String gcmRegistrationId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private boolean webSocketChannel;
|
private boolean webSocketChannel;
|
||||||
|
|
||||||
public GcmRegistrationId() {}
|
public GcmRegistrationId() {}
|
||||||
|
@ -552,7 +553,10 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AttachmentDescriptor {
|
private static class AttachmentDescriptor {
|
||||||
|
@JsonProperty
|
||||||
private long id;
|
private long id;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private String location;
|
private String location;
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
|
|
|
@ -16,10 +16,13 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.internal.push;
|
package org.whispersystems.textsecure.internal.push;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class StaleDevices {
|
public class StaleDevices {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private List<Integer> staleDevices;
|
private List<Integer> staleDevices;
|
||||||
|
|
||||||
public List<Integer> getStaleDevices() {
|
public List<Integer> getStaleDevices() {
|
||||||
|
|
|
@ -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> T fromJson(String json, Class<T> 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<IdentityKey> {
|
||||||
|
@Override
|
||||||
|
public void serialize(IdentityKey value, JsonGenerator gen, SerializerProvider serializers)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class IdentityKeyDeserializer extends JsonDeserializer<IdentityKey> {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
BIN
res/drawable-xxhdpi/ic_error_red_24dp.png
Normal file
BIN
res/drawable-xxhdpi/ic_error_red_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 974 B |
BIN
res/drawable-xxhdpi/ic_error_white_18dp.png
Normal file
BIN
res/drawable-xxhdpi/ic_error_white_18dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 659 B |
BIN
res/drawable-xxhdpi/ic_info_outline_grey600_24dp.png
Normal file
BIN
res/drawable-xxhdpi/ic_info_outline_grey600_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-xxhdpi/ic_refresh_white_18dp.png
Normal file
BIN
res/drawable-xxhdpi/ic_refresh_white_18dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 693 B |
16
res/drawable/error_round.xml
Normal file
16
res/drawable/error_round.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="#FFD32F2F" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="#FFF44336" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
16
res/drawable/info_round.xml
Normal file
16
res/drawable/info_round.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="#ff145c95" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="#ff2090ea" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/conversation_item_parent"
|
<LinearLayout android:id="@+id/conversation_item_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_toRightOf="@id/triangle_tick"
|
android:layout_toRightOf="@id/triangle_tick"
|
||||||
android:background="?conversation_item_received_background"
|
android:background="?conversation_item_received_background"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="2dip"
|
android:paddingTop="2dip"
|
||||||
android:paddingLeft="8dp"
|
android:paddingLeft="8dp"
|
||||||
android:paddingRight="5dp"
|
android:paddingRight="5dp"
|
||||||
|
@ -153,7 +153,7 @@
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/indicators_parent"
|
<LinearLayout android:id="@+id/indicators_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.thoughtcrime.securesms.ConversationItem
|
<org.thoughtcrime.securesms.ConversationItem
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/conversation_item"
|
android:id="@+id/conversation_item"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -17,7 +18,7 @@
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/indicators_parent"
|
<LinearLayout android:id="@+id/indicators_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="left|center_vertical"
|
android:gravity="left|center_vertical"
|
||||||
android:layout_marginLeft="6dp"
|
android:layout_marginLeft="6dp"
|
||||||
|
@ -28,25 +29,28 @@
|
||||||
android:id="@+id/sms_failed_indicator"
|
android:id="@+id/sms_failed_indicator"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_action_warning_red"
|
android:src="@drawable/ic_error_red_24dp"
|
||||||
|
tools:visibility="visible"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:contentDescription="@string/conversation_item_sent__send_failed_indicator_description"/>
|
android:contentDescription="@string/conversation_item_sent__send_failed_indicator_description" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pending_approval_indicator"
|
android:id="@+id/pending_approval_indicator"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_dialog_info_holo_light"
|
android:src="@drawable/ic_info_outline_grey600_24dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:contentDescription="@string/conversation_item_sent__pending_approval_description"/>
|
android:contentDescription="@string/conversation_item_sent__pending_approval_description"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/conversation_item_parent"
|
<LinearLayout android:id="@+id/conversation_item_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:background="?conversation_item_sent_background"
|
android:background="?conversation_item_sent_push_background"
|
||||||
android:paddingRight="10dip"
|
android:paddingRight="10dip"
|
||||||
android:paddingLeft="10dip"
|
android:paddingLeft="10dip"
|
||||||
android:layout_marginLeft="50dp"
|
android:layout_marginLeft="50dp"
|
||||||
|
@ -62,7 +66,8 @@
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textColor="?conversation_sent_text_primary_color"
|
android:textColor="?conversation_sent_text_primary_color"
|
||||||
android:textColorLink="?conversation_sent_text_primary_color"
|
android:textColorLink="?conversation_sent_text_primary_color"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp"
|
||||||
|
tools:text="Lorem ipsum mango dolor coconut papaya" />
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/mms_view"
|
android:id="@+id/mms_view"
|
||||||
|
@ -116,7 +121,7 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout android:layout_width="wrap_content"
|
<LinearLayout android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingTop="0dip"
|
android:paddingTop="0dip"
|
||||||
android:layout_gravity="right">
|
android:layout_gravity="right">
|
||||||
|
|
24
res/layout/message_details_activity.xml
Normal file
24
res/layout/message_details_activity.xml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout android:id="@+id/item_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?conversation_background"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingBottom="15dp"
|
||||||
|
android:elevation="2dp" />
|
||||||
|
|
||||||
|
<ListView android:id="@+id/recipients_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
tools:listitem="@layout/conversation_item_details_item" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
76
res/layout/message_details_header.xml
Normal file
76
res/layout/message_details_header.xml
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TableRow android:id="@+id/sent_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/message_details_table_row_pad">
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/message_details_header__sent"
|
||||||
|
android:gravity="right"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView android:id="@+id/sent_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/message_details_table_row_pad"
|
||||||
|
tools:text="Jan 18, 9:29AM" />
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow android:id="@+id/received_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/message_details_table_row_pad">
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/message_details_header__received"
|
||||||
|
android:gravity="right"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView android:id="@+id/received_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/message_details_table_row_pad"
|
||||||
|
tools:text="Jan 18, 9:31AM" />
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/message_details_table_row_pad">
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/message_details_header__via"
|
||||||
|
android:gravity="right"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView android:id="@+id/transport"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/message_details_table_row_pad"
|
||||||
|
tools:text="Push (TextSecure)" />
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/message_details_table_row_pad">
|
||||||
|
|
||||||
|
<TextView android:id="@+id/tofrom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="@string/message_details_header__to"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
</TableLayout>
|
87
res/layout/message_details_recipient.xml
Normal file
87
res/layout/message_details_recipient.xml
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.MessageRecipientListItem
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.makeramen.RoundedImageView
|
||||||
|
android:id="@+id/contact_photo_image"
|
||||||
|
android:foreground="@drawable/contact_photo_background"
|
||||||
|
app:riv_oval="true"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:layout_marginBottom="3dp"
|
||||||
|
android:layout_marginRight="10dp"
|
||||||
|
android:cropToPadding="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@drawable/ic_contact_picture"
|
||||||
|
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
|
||||||
|
|
||||||
|
<LinearLayout android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dip"
|
||||||
|
android:layout_marginBottom="4dip"
|
||||||
|
android:layout_toRightOf="@id/contact_photo_image"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView android:id="@+id/from"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?attr/conversation_list_item_contact_color"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
tools:text="Jules Bonnot"
|
||||||
|
android:ellipsize="marquee" />
|
||||||
|
|
||||||
|
<TextView android:id="@+id/error_description"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFF44336"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
tools:text="New identity" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button android:id="@+id/conflict_button"
|
||||||
|
android:layout_width="60sp"
|
||||||
|
android:layout_height="38sp"
|
||||||
|
style="@style/ErrorButton"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:drawableLeft="@drawable/ic_error_white_18dp"
|
||||||
|
android:text="FIX" />
|
||||||
|
|
||||||
|
<Button android:id="@+id/resend_button"
|
||||||
|
android:layout_width="85sp"
|
||||||
|
android:layout_height="38sp"
|
||||||
|
style="@style/InfoButton"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:drawableLeft="@drawable/ic_refresh_white_18dp"
|
||||||
|
android:text="RESEND" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</org.thoughtcrime.securesms.MessageRecipientListItem>
|
|
@ -17,4 +17,5 @@
|
||||||
<dimen name="conversation_activity_compose_padding">12dp</dimen>
|
<dimen name="conversation_activity_compose_padding">12dp</dimen>
|
||||||
|
|
||||||
<integer name="media_overview_cols">3</integer>
|
<integer name="media_overview_cols">3</integer>
|
||||||
|
<dimen name="message_details_table_row_pad">10dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -59,10 +59,20 @@
|
||||||
<string name="AttachmentTypeSelectorAdapter_audio">Audio</string>
|
<string name="AttachmentTypeSelectorAdapter_audio">Audio</string>
|
||||||
<string name="AttachmentTypeSelectorAdapter_contact">Contact info</string>
|
<string name="AttachmentTypeSelectorAdapter_contact">Contact info</string>
|
||||||
|
|
||||||
|
<!-- ConfirmIdentityDialog -->
|
||||||
|
<string name="ConfirmIdentityDialog_the_signature_on_this_key_exchange_is_different">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.
|
||||||
|
</string>
|
||||||
|
<string name="ConfirmIdentityDialog_you_may_wish_to_verify_this_contact">You may wish to verify
|
||||||
|
this contact.
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- ConversationItem -->
|
<!-- ConversationItem -->
|
||||||
<string name="ConversationItem_message_size_d_kb">Message size: %d KB</string>
|
<string name="ConversationItem_message_size_d_kb">Message size: %d KB</string>
|
||||||
<string name="ConversationItem_expires_s">Expires: %s</string>
|
<string name="ConversationItem_expires_s">Expires: %s</string>
|
||||||
<string name="ConversationItem_error_sending_message">Error sending message</string>
|
<string name="ConversationItem_error_not_delivered">Not delivered</string>
|
||||||
<string name="ConversationItem_view_secure_media_question">View secure media?</string>
|
<string name="ConversationItem_view_secure_media_question">View secure media?</string>
|
||||||
<string name="ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning">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?</string>
|
<string name="ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning">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?</string>
|
||||||
<string name="ConversationItem_error_received_stale_key_exchange_message">Error, received stale key exchange message.</string>
|
<string name="ConversationItem_error_received_stale_key_exchange_message">Error, received stale key exchange message.</string>
|
||||||
|
@ -70,6 +80,7 @@
|
||||||
<string name="ConversationItem_group_action_left">%1$s has left the group.</string>
|
<string name="ConversationItem_group_action_left">%1$s has left the group.</string>
|
||||||
<string name="ConversationItem_click_to_approve_sms">Tap for SMS fallback</string>
|
<string name="ConversationItem_click_to_approve_sms">Tap for SMS fallback</string>
|
||||||
<string name="ConversationItem_click_to_approve_mms">Tap for MMS fallback</string>
|
<string name="ConversationItem_click_to_approve_mms">Tap for MMS fallback</string>
|
||||||
|
<string name="ConversationItem_click_for_details">Tap for details</string>
|
||||||
<string name="ConversationItem_click_to_approve_unencrypted">Tap for unsecured fallback</string>
|
<string name="ConversationItem_click_to_approve_unencrypted">Tap for unsecured fallback</string>
|
||||||
<string name="ConversationItem_click_to_approve_sms_dialog_title">Fallback to SMS?</string>
|
<string name="ConversationItem_click_to_approve_sms_dialog_title">Fallback to SMS?</string>
|
||||||
<string name="ConversationItem_click_to_approve_mms_dialog_title">Fallback to MMS?</string>
|
<string name="ConversationItem_click_to_approve_mms_dialog_title">Fallback to MMS?</string>
|
||||||
|
@ -126,8 +137,8 @@
|
||||||
<string name="ConversationFragment_unable_to_write_to_sd_card_exclamation">Unable to write to storage!</string>
|
<string name="ConversationFragment_unable_to_write_to_sd_card_exclamation">Unable to write to storage!</string>
|
||||||
<string name="ConversationFragment_saving_attachment">Saving attachment</string>
|
<string name="ConversationFragment_saving_attachment">Saving attachment</string>
|
||||||
<string name="ConversationFragment_saving_attachment_to_sd_card">Saving attachment to storage...</string>
|
<string name="ConversationFragment_saving_attachment_to_sd_card">Saving attachment to storage...</string>
|
||||||
<string name="ConversationFragment_pending">PENDING</string>
|
<string name="ConversationFragment_pending">Pending...</string>
|
||||||
<string name="ConversationFragment_push">PUSH</string>
|
<string name="ConversationFragment_push">Data (TextSecure)</string>
|
||||||
<string name="ConversationFragment_mms">MMS</string>
|
<string name="ConversationFragment_mms">MMS</string>
|
||||||
<string name="ConversationFragment_sms">SMS</string>
|
<string name="ConversationFragment_sms">SMS</string>
|
||||||
<string name="ConversationFragment_deleting">Deleting...</string>
|
<string name="ConversationFragment_deleting">Deleting...</string>
|
||||||
|
@ -225,6 +236,10 @@
|
||||||
<string name="KeyScanningActivity_install_barcode_Scanner">Install Barcode Scanner?</string>
|
<string name="KeyScanningActivity_install_barcode_Scanner">Install Barcode Scanner?</string>
|
||||||
<string name="KeyScanningActivity_this_application_requires_barcode_scanner_would_you_like_to_install_it">TextSecure needs Barcode Scanner for QR codes.</string>
|
<string name="KeyScanningActivity_this_application_requires_barcode_scanner_would_you_like_to_install_it">TextSecure needs Barcode Scanner for QR codes.</string>
|
||||||
|
|
||||||
|
<!-- MessageDetailsRecipient -->
|
||||||
|
<string name="MessageDetailsRecipient_failed_to_send">Failed to send</string>
|
||||||
|
<string name="MessageDetailsRecipient_new_identity">New identity</string>
|
||||||
|
|
||||||
<!-- MmsDownloader -->
|
<!-- MmsDownloader -->
|
||||||
<string name="MmsDownloader_error_storing_mms">Error storing MMS!</string>
|
<string name="MmsDownloader_error_storing_mms">Error storing MMS!</string>
|
||||||
<string name="MmsDownloader_error_connecting_to_mms_provider">Error connecting to MMS provider...</string>
|
<string name="MmsDownloader_error_connecting_to_mms_provider">Error connecting to MMS provider...</string>
|
||||||
|
@ -642,6 +657,14 @@
|
||||||
<string name="verify_identity_activity__their_identity_they_read">Their identity (they read):</string>
|
<string name="verify_identity_activity__their_identity_they_read">Their identity (they read):</string>
|
||||||
<string name="verify_identity_activity__your_identity_you_read">Your identity (you read):</string>
|
<string name="verify_identity_activity__your_identity_you_read">Your identity (you read):</string>
|
||||||
|
|
||||||
|
<!-- message_details_header -->
|
||||||
|
<string name="message_details_header__sent">Sent</string>
|
||||||
|
<string name="message_details_header__received">Received</string>
|
||||||
|
<string name="message_details_header__via">Via</string>
|
||||||
|
<string name="message_details_header__to">To:</string>
|
||||||
|
<string name="message_details_header__from">From:</string>
|
||||||
|
<string name="message_details_header__with">With:</string>
|
||||||
|
|
||||||
<!-- AndroidManifest.xml -->
|
<!-- AndroidManifest.xml -->
|
||||||
<string name="AndroidManifest__create_passphrase">Create passphrase</string>
|
<string name="AndroidManifest__create_passphrase">Create passphrase</string>
|
||||||
<string name="AndroidManifest__enter_passphrase">Enter passphrase</string>
|
<string name="AndroidManifest__enter_passphrase">Enter passphrase</string>
|
||||||
|
|
|
@ -161,4 +161,19 @@
|
||||||
<item name="android:textColor">#ff999999</item>
|
<item name="android:textColor">#ff999999</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="MaterialButton">
|
||||||
|
<item name="android:elevation">1dp</item>
|
||||||
|
<item name="android:translationZ">1dp</item>
|
||||||
|
<item name="android:textColor">@color/white</item>
|
||||||
|
<item name="android:textSize">12sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="InfoButton" parent="@style/MaterialButton">
|
||||||
|
<item name="android:background">@drawable/info_round</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ErrorButton" parent="@style/MaterialButton">
|
||||||
|
<item name="android:background">@drawable/error_round</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -72,9 +72,7 @@ public class ApplicationContext extends Application implements DependencyInjecto
|
||||||
return jobManager;
|
return jobManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void initializeRandomNumberFix() {
|
private void initializeRandomNumberFix() {
|
||||||
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
|
|
||||||
PRNGFixes.apply();
|
PRNGFixes.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
216
src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java
Normal file
216
src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java
Normal file
|
@ -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<Void, Void, Void>()
|
||||||
|
{
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -62,7 +62,6 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
|
||||||
private final Set<MessageRecord> batchSelected = Collections.synchronizedSet(new HashSet<MessageRecord>());
|
private final Set<MessageRecord> batchSelected = Collections.synchronizedSet(new HashSet<MessageRecord>());
|
||||||
|
|
||||||
private final SelectionClickListener selectionClickListener;
|
private final SelectionClickListener selectionClickListener;
|
||||||
private final Handler failedIconClickHandler;
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final MasterSecret masterSecret;
|
private final MasterSecret masterSecret;
|
||||||
private final boolean groupThread;
|
private final boolean groupThread;
|
||||||
|
@ -70,13 +69,12 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
|
||||||
private final LayoutInflater inflater;
|
private final LayoutInflater inflater;
|
||||||
|
|
||||||
public ConversationAdapter(Context context, MasterSecret masterSecret, SelectionClickListener selectionClickListener,
|
public ConversationAdapter(Context context, MasterSecret masterSecret, SelectionClickListener selectionClickListener,
|
||||||
Handler failedIconClickHandler, boolean groupThread, boolean pushDestination)
|
boolean groupThread, boolean pushDestination)
|
||||||
{
|
{
|
||||||
super(context, null, 0);
|
super(context, null, 0);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.masterSecret = masterSecret;
|
this.masterSecret = masterSecret;
|
||||||
this.selectionClickListener = selectionClickListener;
|
this.selectionClickListener = selectionClickListener;
|
||||||
this.failedIconClickHandler = failedIconClickHandler;
|
|
||||||
this.groupThread = groupThread;
|
this.groupThread = groupThread;
|
||||||
this.pushDestination = pushDestination;
|
this.pushDestination = pushDestination;
|
||||||
this.inflater = LayoutInflater.from(context);
|
this.inflater = LayoutInflater.from(context);
|
||||||
|
@ -90,7 +88,7 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
|
||||||
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
|
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
|
||||||
|
|
||||||
item.set(masterSecret, messageRecord, batchSelected, selectionClickListener,
|
item.set(masterSecret, messageRecord, batchSelected, selectionClickListener,
|
||||||
failedIconClickHandler, groupThread, pushDestination);
|
groupThread, pushDestination);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.support.v4.app.ListFragment;
|
import android.support.v4.app.ListFragment;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.Loader;
|
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.app.ActionBarActivity;
|
||||||
import android.support.v7.view.ActionMode;
|
import android.support.v7.view.ActionMode;
|
||||||
import android.text.ClipboardManager;
|
import android.text.ClipboardManager;
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
@ -30,6 +28,7 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
|
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
|
||||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
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;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||||
|
|
||||||
import java.sql.Date;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -103,7 +100,6 @@ public class ConversationFragment extends ListFragment
|
||||||
private void initializeListAdapter() {
|
private void initializeListAdapter() {
|
||||||
if (this.recipients != null && this.threadId != -1) {
|
if (this.recipients != null && this.threadId != -1) {
|
||||||
this.setListAdapter(new ConversationAdapter(getActivity(), masterSecret, selectionClickListener,
|
this.setListAdapter(new ConversationAdapter(getActivity(), masterSecret, selectionClickListener,
|
||||||
new FailedIconClickHandler(),
|
|
||||||
(!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(),
|
(!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(),
|
||||||
DirectoryHelper.isPushDestination(getActivity(), this.recipients)));
|
DirectoryHelper.isPushDestination(getActivity(), this.recipients)));
|
||||||
getListView().setRecyclerListener((ConversationAdapter)getListAdapter());
|
getListView().setRecyclerListener((ConversationAdapter)getListAdapter());
|
||||||
|
@ -218,46 +214,11 @@ public class ConversationFragment extends ListFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDisplayDetails(MessageRecord message) {
|
private void handleDisplayDetails(MessageRecord message) {
|
||||||
long dateReceived = message.getDateReceived();
|
Intent intent = new Intent(getActivity(), MessageDetailsActivity.class);
|
||||||
long dateSent = message.getDateSent();
|
intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret);
|
||||||
|
intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, message.getId());
|
||||||
String transport;
|
intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, message.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT);
|
||||||
|
startActivity(intent);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleForwardMessage(MessageRecord message) {
|
private void handleForwardMessage(MessageRecord message) {
|
||||||
|
@ -315,15 +276,6 @@ public class ConversationFragment extends ListFragment
|
||||||
((CursorAdapter)getListAdapter()).changeCursor(null);
|
((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 interface ConversationFragmentListener {
|
||||||
public void setComposeText(String text);
|
public void setComposeText(String text);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
@ -119,7 +120,6 @@ public class ConversationItem extends LinearLayout {
|
||||||
private FutureTaskListener<SlideDeck> slideDeckListener;
|
private FutureTaskListener<SlideDeck> slideDeckListener;
|
||||||
private TypedArray backgroundDrawables;
|
private TypedArray backgroundDrawables;
|
||||||
|
|
||||||
private final FailedIconClickListener failedIconClickListener = new FailedIconClickListener();
|
|
||||||
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
|
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
|
||||||
private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener();
|
private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener();
|
||||||
private final ClickListener clickListener = new ClickListener();
|
private final ClickListener clickListener = new ClickListener();
|
||||||
|
@ -158,20 +158,18 @@ public class ConversationItem extends LinearLayout {
|
||||||
this.backgroundDrawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
|
this.backgroundDrawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
|
||||||
|
|
||||||
setOnClickListener(clickListener);
|
setOnClickListener(clickListener);
|
||||||
if (failedImage != null) failedImage.setOnClickListener(failedIconClickListener);
|
|
||||||
if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
|
if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
|
||||||
if (mmsThumbnail != null) mmsThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
|
if (mmsThumbnail != null) mmsThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(MasterSecret masterSecret, MessageRecord messageRecord,
|
public void set(MasterSecret masterSecret, MessageRecord messageRecord,
|
||||||
Set<MessageRecord> batchSelected, SelectionClickListener selectionClickListener,
|
Set<MessageRecord> batchSelected, SelectionClickListener selectionClickListener,
|
||||||
Handler failedIconHandler, boolean groupThread, boolean pushDestination)
|
boolean groupThread, boolean pushDestination)
|
||||||
{
|
{
|
||||||
this.masterSecret = masterSecret;
|
this.masterSecret = masterSecret;
|
||||||
this.messageRecord = messageRecord;
|
this.messageRecord = messageRecord;
|
||||||
this.batchSelected = batchSelected;
|
this.batchSelected = batchSelected;
|
||||||
this.selectionClickListener = selectionClickListener;
|
this.selectionClickListener = selectionClickListener;
|
||||||
this.failedIconHandler = failedIconHandler;
|
|
||||||
this.groupThread = groupThread;
|
this.groupThread = groupThread;
|
||||||
this.pushDestination = pushDestination;
|
this.pushDestination = pushDestination;
|
||||||
|
|
||||||
|
@ -223,10 +221,10 @@ public class ConversationItem extends LinearLayout {
|
||||||
if (messageRecord.isOutgoing()) {
|
if (messageRecord.isOutgoing()) {
|
||||||
final int background;
|
final int background;
|
||||||
final int triangleBackground;
|
final int triangleBackground;
|
||||||
if (messageRecord.isPending() && pushDestination && !messageRecord.isForcedSms()) {
|
if ((messageRecord.isPending() || messageRecord.isFailed()) && pushDestination && !messageRecord.isForcedSms()) {
|
||||||
background = SENT_PUSH_PENDING;
|
background = SENT_PUSH_PENDING;
|
||||||
triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
|
triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
|
||||||
} else if (messageRecord.isPending() || messageRecord.isPendingSmsFallback()) {
|
} else if (messageRecord.isPending() || messageRecord.isFailed() || messageRecord.isPendingSmsFallback()) {
|
||||||
background = SENT_SMS_PENDING;
|
background = SENT_SMS_PENDING;
|
||||||
triangleBackground = SENT_SMS_PENDING_TRIANGLE;
|
triangleBackground = SENT_SMS_PENDING_TRIANGLE;
|
||||||
} else if (messageRecord.isPush()) {
|
} else if (messageRecord.isPush()) {
|
||||||
|
@ -279,10 +277,9 @@ public class ConversationItem extends LinearLayout {
|
||||||
|
|
||||||
private void setStatusIcons(MessageRecord messageRecord) {
|
private void setStatusIcons(MessageRecord messageRecord) {
|
||||||
failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE);
|
failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE);
|
||||||
if (messageRecord.isOutgoing()) {
|
// pendingIndicator.setVisibility(View.GONE);
|
||||||
pendingIndicator.setVisibility(messageRecord.isPendingSmsFallback() ? View.VISIBLE : View.GONE);
|
if (messageRecord.isOutgoing()) indicatorText.setVisibility(View.GONE);
|
||||||
indicatorText.setVisibility(messageRecord.isPendingSmsFallback() ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE);
|
secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE);
|
||||||
bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, messageRecord.isKeyExchange() ? R.drawable.ic_menu_login : 0, 0);
|
bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, messageRecord.isKeyExchange() ? R.drawable.ic_menu_login : 0, 0);
|
||||||
deliveryImage.setVisibility(!messageRecord.isKeyExchange() && messageRecord.isDelivered() ? View.VISIBLE : View.GONE);
|
deliveryImage.setVisibility(!messageRecord.isKeyExchange() && messageRecord.isDelivered() ? View.VISIBLE : View.GONE);
|
||||||
|
@ -291,25 +288,37 @@ public class ConversationItem extends LinearLayout {
|
||||||
mmsDownloadButton.setVisibility(View.GONE);
|
mmsDownloadButton.setVisibility(View.GONE);
|
||||||
mmsDownloadingLabel.setVisibility(View.GONE);
|
mmsDownloadingLabel.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (messageRecord.isFailed()) {
|
if (messageRecord.isFailed()) setFailedStatusIcons();
|
||||||
dateText.setText(R.string.ConversationItem_error_sending_message);
|
else if (messageRecord.isPendingSmsFallback()) setFallbackStatusIcons();
|
||||||
} else if (messageRecord.isPendingSmsFallback() && indicatorText != null) {
|
else if (messageRecord.isPending()) dateText.setText(" ··· ");
|
||||||
dateText.setText("");
|
else setSentStatusIcons();
|
||||||
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 {
|
private void setSentStatusIcons() {
|
||||||
indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
|
final long timestamp;
|
||||||
}
|
|
||||||
} else if (messageRecord.isPending()) {
|
if (messageRecord.isPush()) timestamp = messageRecord.getDateSent();
|
||||||
dateText.setText(" ··· ");
|
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 {
|
} else {
|
||||||
final long timestamp;
|
indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
|
||||||
|
|
||||||
if (messageRecord.isPush()) timestamp = messageRecord.getDateSent();
|
|
||||||
else timestamp = messageRecord.getDateReceived();
|
|
||||||
|
|
||||||
dateText.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), timestamp));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +332,9 @@ public class ConversationItem extends LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setEvents(MessageRecord messageRecord) {
|
private void setEvents(MessageRecord messageRecord) {
|
||||||
setClickable(messageRecord.isPendingSmsFallback() ||
|
setClickable(messageRecord.isPendingSmsFallback() ||
|
||||||
|
messageRecord.hasNetworkFailures() ||
|
||||||
|
messageRecord.isIdentityMismatchFailure() ||
|
||||||
(messageRecord.isKeyExchange() &&
|
(messageRecord.isKeyExchange() &&
|
||||||
!messageRecord.isCorruptedKeyExchange() &&
|
!messageRecord.isCorruptedKeyExchange() &&
|
||||||
!messageRecord.isOutgoing()));
|
!messageRecord.isOutgoing()));
|
||||||
|
@ -529,7 +540,7 @@ public class ConversationItem extends LinearLayout {
|
||||||
private class MmsDownloadClickListener implements View.OnClickListener {
|
private class MmsDownloadClickListener implements View.OnClickListener {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
NotificationMmsMessageRecord notificationRecord = (NotificationMmsMessageRecord)messageRecord;
|
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);
|
mmsDownloadButton.setVisibility(View.GONE);
|
||||||
mmsDownloadingLabel.setVisibility(View.VISIBLE);
|
mmsDownloadingLabel.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
@ -562,13 +573,22 @@ public class ConversationItem extends LinearLayout {
|
||||||
|
|
||||||
private class ClickListener implements View.OnClickListener {
|
private class ClickListener implements View.OnClickListener {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (messageRecord.isKeyExchange() &&
|
if (messageRecord.isIdentityMismatchFailure() || messageRecord.hasNetworkFailures()) {
|
||||||
!messageRecord.isOutgoing() &&
|
Intent intent = new Intent(context, MessageDetailsActivity.class);
|
||||||
!messageRecord.isProcessedKeyExchange() &&
|
intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret);
|
||||||
!messageRecord.isStaleKeyExchange())
|
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();
|
handleKeyExchangeClicked();
|
||||||
else if (messageRecord.isPendingSmsFallback())
|
} else if (messageRecord.isPendingSmsFallback()) {
|
||||||
handleMessageApproval();
|
handleMessageApproval();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,17 +17,9 @@
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Handler;
|
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.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
@ -39,6 +31,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.Emoji;
|
import org.thoughtcrime.securesms.util.Emoji;
|
||||||
|
import org.thoughtcrime.securesms.util.RecipientViewUtil;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -66,7 +59,6 @@ public class ConversationListItem extends RelativeLayout
|
||||||
private TextView subjectView;
|
private TextView subjectView;
|
||||||
private TextView fromView;
|
private TextView fromView;
|
||||||
private TextView dateView;
|
private TextView dateView;
|
||||||
private long count;
|
|
||||||
private boolean read;
|
private boolean read;
|
||||||
private ImageView contactPhotoImage;
|
private ImageView contactPhotoImage;
|
||||||
|
|
||||||
|
@ -98,12 +90,11 @@ public class ConversationListItem extends RelativeLayout
|
||||||
this.selectedThreads = selectedThreads;
|
this.selectedThreads = selectedThreads;
|
||||||
this.recipients = thread.getRecipients();
|
this.recipients = thread.getRecipients();
|
||||||
this.threadId = thread.getThreadId();
|
this.threadId = thread.getThreadId();
|
||||||
this.count = thread.getCount();
|
|
||||||
this.read = thread.isRead();
|
this.read = thread.isRead();
|
||||||
this.distributionType = thread.getDistributionType();
|
this.distributionType = thread.getDistributionType();
|
||||||
|
|
||||||
this.recipients.addListener(this);
|
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(),
|
this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(),
|
||||||
Emoji.EMOJI_SMALL,
|
Emoji.EMOJI_SMALL,
|
||||||
|
@ -118,7 +109,7 @@ public class ConversationListItem extends RelativeLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
setBackground(read, batchMode);
|
setBackground(read, batchMode);
|
||||||
setContactPhoto(this.recipients.getPrimaryRecipient());
|
RecipientViewUtil.setContactPhoto(context, contactPhotoImage, recipients.getPrimaryRecipient(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unbind() {
|
public void unbind() {
|
||||||
|
@ -130,28 +121,6 @@ public class ConversationListItem extends RelativeLayout
|
||||||
contactPhotoImage.setVisibility(View.VISIBLE);
|
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) {
|
private void setBackground(boolean read, boolean batch) {
|
||||||
int[] attributes = new int[]{R.attr.conversation_list_item_background_selected,
|
int[] attributes = new int[]{R.attr.conversation_list_item_background_selected,
|
||||||
R.attr.conversation_list_item_background_read,
|
R.attr.conversation_list_item_background_read,
|
||||||
|
@ -170,38 +139,6 @@ public class ConversationListItem extends RelativeLayout
|
||||||
drawables.recycle();
|
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() {
|
public Recipients getRecipients() {
|
||||||
return recipients;
|
return recipients;
|
||||||
}
|
}
|
||||||
|
@ -219,8 +156,8 @@ public class ConversationListItem extends RelativeLayout
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
ConversationListItem.this.fromView.setText(formatFrom(recipients, count, read));
|
ConversationListItem.this.fromView.setText(RecipientViewUtil.formatFrom(context, recipients, read));
|
||||||
setContactPhoto(ConversationListItem.this.recipients.getPrimaryRecipient());
|
RecipientViewUtil.setContactPhoto(context, contactPhotoImage, recipients.getPrimaryRecipient(), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
255
src/org/thoughtcrime/securesms/MessageDetailsActivity.java
Normal file
255
src/org/thoughtcrime/securesms/MessageDetailsActivity.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<Cursor> {
|
||||||
|
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<MessageRecord>(), 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<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new MessageDetailsLoader(this, getIntent().getStringExtra(TYPE_EXTRA),
|
||||||
|
getIntent().getLongExtra(MESSAGE_ID_EXTRA, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||||
|
final MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA));
|
||||||
|
new MessageRecipientAsyncTask(this, messageRecord).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
recipientsList.setAdapter(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessageRecipientAsyncTask extends AsyncTask<Void,Void,Recipients> {
|
||||||
|
private WeakReference<Context> 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<Recipient>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
177
src/org/thoughtcrime/securesms/MessageRecipientListItem.java
Normal file
177
src/org/thoughtcrime/securesms/MessageRecipientListItem.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<Void,Void,Void> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -236,7 +236,7 @@ public class ReceiveKeyActivity extends BaseActivity {
|
||||||
|
|
||||||
ApplicationContext.getInstance(context)
|
ApplicationContext.getInstance(context)
|
||||||
.getJobManager()
|
.getJobManager()
|
||||||
.add(new PushDecryptJob(context, pushId, message.getSender()));
|
.add(new PushDecryptJob(context, pushId, messageId, message.getSender()));
|
||||||
|
|
||||||
smsDatabase.deleteMessage(messageId);
|
smsDatabase.deleteMessage(messageId);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
@ -17,20 +17,11 @@
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
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.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
@ -39,11 +30,7 @@ import com.makeramen.RoundedImageView;
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.RecipientViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
|
||||||
import org.thoughtcrime.securesms.util.Emoji;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple view to show the recipients of an open conversation
|
* A simple view to show the recipients of an open conversation
|
||||||
|
@ -51,7 +38,7 @@ import java.util.Set;
|
||||||
* @author Jake McGinty
|
* @author Jake McGinty
|
||||||
*/
|
*/
|
||||||
public class ShareListItem extends RelativeLayout
|
public class ShareListItem extends RelativeLayout
|
||||||
implements Recipient.RecipientModifiedListener
|
implements Recipient.RecipientModifiedListener
|
||||||
{
|
{
|
||||||
private final static String TAG = ShareListItem.class.getSimpleName();
|
private final static String TAG = ShareListItem.class.getSimpleName();
|
||||||
|
|
||||||
|
@ -87,21 +74,16 @@ public class ShareListItem extends RelativeLayout
|
||||||
this.distributionType = thread.getDistributionType();
|
this.distributionType = thread.getDistributionType();
|
||||||
|
|
||||||
this.recipients.addListener(this);
|
this.recipients.addListener(this);
|
||||||
this.fromView.setText(formatFrom(recipients));
|
this.fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipients));
|
||||||
|
|
||||||
setBackground();
|
setBackground();
|
||||||
setContactPhoto(this.recipients.getPrimaryRecipient());
|
RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, this.recipients.getPrimaryRecipient(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unbind() {
|
public void unbind() {
|
||||||
if (this.recipients != null) this.recipients.removeListener(this);
|
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() {
|
private void setBackground() {
|
||||||
int[] attributes = new int[]{R.attr.conversation_list_item_background_read};
|
int[] attributes = new int[]{R.attr.conversation_list_item_background_read};
|
||||||
TypedArray drawables = context.obtainStyledAttributes(attributes);
|
TypedArray drawables = context.obtainStyledAttributes(attributes);
|
||||||
|
@ -111,26 +93,6 @@ public class ShareListItem extends RelativeLayout
|
||||||
drawables.recycle();
|
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() {
|
public Recipients getRecipients() {
|
||||||
return recipients;
|
return recipients;
|
||||||
}
|
}
|
||||||
|
@ -148,8 +110,8 @@ public class ShareListItem extends RelativeLayout
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
fromView.setText(formatFrom(recipients));
|
fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipients));
|
||||||
setContactPhoto(recipients.getPrimaryRecipient());
|
RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipients.getPrimaryRecipient(), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,10 @@ package org.thoughtcrime.securesms.crypto;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
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.crypto.storage.TextSecurePreKeyStore;
|
||||||
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
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.Curve;
|
||||||
import org.whispersystems.libaxolotl.ecc.Curve25519;
|
import org.whispersystems.libaxolotl.ecc.Curve25519;
|
||||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
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.PreKeyRecord;
|
||||||
import org.whispersystems.libaxolotl.state.PreKeyStore;
|
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 org.whispersystems.libaxolotl.util.Medium;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -109,7 +110,7 @@ public class PreKeyUtil {
|
||||||
try {
|
try {
|
||||||
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
|
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
|
||||||
FileOutputStream fout = new FileOutputStream(nextFile);
|
FileOutputStream fout = new FileOutputStream(nextFile);
|
||||||
fout.write(new Gson().toJson(new PreKeyIndex(id)).getBytes());
|
fout.write(JsonUtils.toJson(new PreKeyIndex(id)).getBytes());
|
||||||
fout.close();
|
fout.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w("PreKeyUtil", e);
|
Log.w("PreKeyUtil", e);
|
||||||
|
@ -120,7 +121,7 @@ public class PreKeyUtil {
|
||||||
try {
|
try {
|
||||||
File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
|
File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
|
||||||
FileOutputStream fout = new FileOutputStream(nextFile);
|
FileOutputStream fout = new FileOutputStream(nextFile);
|
||||||
fout.write(new Gson().toJson(new SignedPreKeyIndex(id)).getBytes());
|
fout.write(JsonUtils.toJson(new SignedPreKeyIndex(id)).getBytes());
|
||||||
fout.close();
|
fout.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w("PreKeyUtil", e);
|
Log.w("PreKeyUtil", e);
|
||||||
|
@ -135,7 +136,7 @@ public class PreKeyUtil {
|
||||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
||||||
} else {
|
} else {
|
||||||
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
|
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
|
||||||
PreKeyIndex index = new Gson().fromJson(reader, PreKeyIndex.class);
|
PreKeyIndex index = JsonUtils.fromJson(reader, PreKeyIndex.class);
|
||||||
reader.close();
|
reader.close();
|
||||||
return index.nextPreKeyId;
|
return index.nextPreKeyId;
|
||||||
}
|
}
|
||||||
|
@ -153,7 +154,7 @@ public class PreKeyUtil {
|
||||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
||||||
} else {
|
} else {
|
||||||
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
|
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
|
||||||
SignedPreKeyIndex index = new Gson().fromJson(reader, SignedPreKeyIndex.class);
|
SignedPreKeyIndex index = JsonUtils.fromJson(reader, SignedPreKeyIndex.class);
|
||||||
reader.close();
|
reader.close();
|
||||||
return index.nextSignedPreKeyId;
|
return index.nextSignedPreKeyId;
|
||||||
}
|
}
|
||||||
|
@ -183,6 +184,7 @@ public class PreKeyUtil {
|
||||||
private static class PreKeyIndex {
|
private static class PreKeyIndex {
|
||||||
public static final String FILE_NAME = "index.dat";
|
public static final String FILE_NAME = "index.dat";
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private int nextPreKeyId;
|
private int nextPreKeyId;
|
||||||
|
|
||||||
public PreKeyIndex() {}
|
public PreKeyIndex() {}
|
||||||
|
@ -195,6 +197,7 @@ public class PreKeyUtil {
|
||||||
private static class SignedPreKeyIndex {
|
private static class SignedPreKeyIndex {
|
||||||
public static final String FILE_NAME = "index.dat";
|
public static final String FILE_NAME = "index.dat";
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
private int nextSignedPreKeyId;
|
private int nextSignedPreKeyId;
|
||||||
|
|
||||||
public SignedPreKeyIndex() {}
|
public SignedPreKeyIndex() {}
|
||||||
|
|
|
@ -25,9 +25,9 @@ import java.util.Set;
|
||||||
|
|
||||||
public abstract class Database {
|
public abstract class Database {
|
||||||
|
|
||||||
protected static final String ID_WHERE = "_id = ?";
|
protected static final String ID_WHERE = "_id = ?";
|
||||||
private static final String CONVERSATION_URI = "content://textsecure/thread/";
|
private static final String CONVERSATION_URI = "content://textsecure/thread/";
|
||||||
private static final String CONVERSATION_LIST_URI = "content://textsecure/conversation-list";
|
private static final String CONVERSATION_LIST_URI = "content://textsecure/conversation-list";
|
||||||
|
|
||||||
protected SQLiteOpenHelper databaseHelper;
|
protected SQLiteOpenHelper databaseHelper;
|
||||||
protected final Context context;
|
protected final Context context;
|
||||||
|
|
|
@ -45,21 +45,22 @@ import ws.com.google.android.mms.ContentType;
|
||||||
|
|
||||||
public class DatabaseFactory {
|
public class DatabaseFactory {
|
||||||
|
|
||||||
private static final int INTRODUCED_IDENTITIES_VERSION = 2;
|
private static final int INTRODUCED_IDENTITIES_VERSION = 2;
|
||||||
private static final int INTRODUCED_INDEXES_VERSION = 3;
|
private static final int INTRODUCED_INDEXES_VERSION = 3;
|
||||||
private static final int INTRODUCED_DATE_SENT_VERSION = 4;
|
private static final int INTRODUCED_DATE_SENT_VERSION = 4;
|
||||||
private static final int INTRODUCED_DRAFTS_VERSION = 5;
|
private static final int INTRODUCED_DRAFTS_VERSION = 5;
|
||||||
private static final int INTRODUCED_NEW_TYPES_VERSION = 6;
|
private static final int INTRODUCED_NEW_TYPES_VERSION = 6;
|
||||||
private static final int INTRODUCED_MMS_BODY_VERSION = 7;
|
private static final int INTRODUCED_MMS_BODY_VERSION = 7;
|
||||||
private static final int INTRODUCED_MMS_FROM_VERSION = 8;
|
private static final int INTRODUCED_MMS_FROM_VERSION = 8;
|
||||||
private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9;
|
private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9;
|
||||||
private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10;
|
private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10;
|
||||||
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
|
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
|
||||||
private static final int INTRODUCED_PUSH_FIX_VERSION = 12;
|
private static final int INTRODUCED_PUSH_FIX_VERSION = 12;
|
||||||
private static final int INTRODUCED_DELIVERY_RECEIPTS = 13;
|
private static final int INTRODUCED_DELIVERY_RECEIPTS = 13;
|
||||||
private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14;
|
private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14;
|
||||||
private static final int INTRODUCED_THUMBNAILS_VERSION = 15;
|
private static final int INTRODUCED_THUMBNAILS_VERSION = 15;
|
||||||
private static final int DATABASE_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 String DATABASE_NAME = "messages.db";
|
||||||
private static final Object lock = new Object();
|
private static final Object lock = new Object();
|
||||||
|
@ -706,8 +707,14 @@ public class DatabaseFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < INTRODUCED_THUMBNAILS_VERSION) {
|
if (oldVersion < INTRODUCED_THUMBNAILS_VERSION) {
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN thumbnail TEXT");
|
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 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();
|
db.setTransactionSuccessful();
|
||||||
|
|
145
src/org/thoughtcrime/securesms/database/MessagingDatabase.java
Normal file
145
src/org/thoughtcrime/securesms/database/MessagingDatabase.java
Normal file
|
@ -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 <D extends Document<I>, I> void removeFromDocument(long messageId, String column, I object, Class<D> clazz) throws IOException {
|
||||||
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
|
database.beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
D document = getDocument(database, messageId, column, clazz);
|
||||||
|
Iterator<I> 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 <T extends Document<I>, I> void addToDocument(long messageId, String column, final I object, Class<T> clazz) throws IOException {
|
||||||
|
List<I> list = new ArrayList<I>() {{
|
||||||
|
add(object);
|
||||||
|
}};
|
||||||
|
|
||||||
|
addToDocument(messageId, column, list, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T extends Document<I>, I> void addToDocument(long messageId, String column, List<I> objects, Class<T> 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 extends Document> D getDocument(SQLiteDatabase database, long messageId,
|
||||||
|
String column, Class<D> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,11 @@ import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.util.Log;
|
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.CharacterSets;
|
||||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||||
|
@ -33,6 +38,8 @@ import java.util.List;
|
||||||
|
|
||||||
public class MmsAddressDatabase extends Database {
|
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 TABLE_NAME = "mms_addresses";
|
||||||
private static final String ID = "_id";
|
private static final String ID = "_id";
|
||||||
private static final String MMS_ID = "mms_id";
|
private static final String MMS_ID = "mms_id";
|
||||||
|
@ -127,6 +134,25 @@ public class MmsAddressDatabase extends Database {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Recipients getRecipientsForId(long messageId) {
|
||||||
|
List<String> numbers = getAddressesForId(messageId);
|
||||||
|
List<Recipient> 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) {
|
public void deleteAddressesForId(long messageId) {
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {messageId+""});
|
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {messageId+""});
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -34,6 +33,10 @@ import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
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.DisplayRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
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.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
import org.thoughtcrime.securesms.util.LRUCache;
|
import org.thoughtcrime.securesms.util.LRUCache;
|
||||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
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.libaxolotl.util.guava.Optional;
|
||||||
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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
|
// 2) How many queries do we make? calling getMediaMessageForId() from within an existing query
|
||||||
// seems wasteful.
|
// 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";
|
public static final String TABLE_NAME = "mms";
|
||||||
static final String DATE_SENT = "date";
|
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_TIME = "d_tm";
|
||||||
private static final String DELIVERY_REPORT = "d_rpt";
|
private static final String DELIVERY_REPORT = "d_rpt";
|
||||||
static final String PART_COUNT = "part_count";
|
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, " +
|
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, " +
|
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, " +
|
STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + RETRIEVE_STATUS + " INTEGER, " +
|
||||||
RETRIEVE_TEXT + " TEXT, " + RETRIEVE_TEXT_CS + " INTEGER, " + READ_STATUS + " INTEGER, " +
|
RETRIEVE_TEXT + " TEXT, " + RETRIEVE_TEXT_CS + " INTEGER, " + READ_STATUS + " INTEGER, " +
|
||||||
CONTENT_CLASS + " INTEGER, " + RESPONSE_TEXT + " TEXT, " + DELIVERY_TIME + " INTEGER, " +
|
CONTENT_CLASS + " INTEGER, " + RESPONSE_TEXT + " TEXT, " + DELIVERY_TIME + " INTEGER, " +
|
||||||
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 = {
|
public static final String[] CREATE_INDEXS = {
|
||||||
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||||
|
@ -150,7 +160,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||||
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
|
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
|
||||||
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
|
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
|
||||||
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
||||||
RECEIPT_COUNT
|
RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
|
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();
|
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTableName() {
|
||||||
|
return TABLE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
public int getMessageCountForThread(long threadId) {
|
public int getMessageCountForThread(long threadId) {
|
||||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
|
@ -181,6 +196,22 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addFailures(long messageId, List<NetworkFailure> 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) {
|
public void incrementDeliveryReceiptCount(String address, long timestamp) {
|
||||||
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
|
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
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) {
|
public void updateResponseStatus(long messageId, int status) {
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
|
@ -330,8 +369,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||||
private void updateMailboxBitmask(long id, long maskOff, long maskOn) {
|
private void updateMailboxBitmask(long id, long maskOff, long maskOn) {
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
db.execSQL("UPDATE " + TABLE_NAME +
|
db.execSQL("UPDATE " + TABLE_NAME +
|
||||||
" SET " + MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + " )" +
|
" SET " + MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + " )" +
|
||||||
" WHERE " + ID + " = ?", new String[] {id + ""});
|
" WHERE " + ID + " = ?", new String[] {id + ""});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markAsOutbox(long messageId) {
|
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));
|
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT));
|
||||||
DisplayRecord.Body body = getBody(cursor);
|
DisplayRecord.Body body = getBody(cursor);
|
||||||
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
|
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
|
||||||
Recipients recipients = getRecipientsFor(address);
|
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES));
|
||||||
|
String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE));
|
||||||
|
|
||||||
|
Recipients recipients = getRecipientsFor(address);
|
||||||
|
List<IdentityKeyMismatch> mismatches = getMismatchedIdentities(mismatchDocument);
|
||||||
|
List<NetworkFailure> networkFailures = getFailures(networkDocument);
|
||||||
|
|
||||||
ListenableFutureTask<SlideDeck> slideDeck = getSlideDeck(masterSecret, id);
|
ListenableFutureTask<SlideDeck> slideDeck = getSlideDeck(masterSecret, id);
|
||||||
|
|
||||||
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||||
addressDeviceId, dateSent, dateReceived, receiptCount,
|
addressDeviceId, dateSent, dateReceived, receiptCount,
|
||||||
threadId, body, slideDeck, partCount, box);
|
threadId, body, slideDeck, partCount, box, mismatches, networkFailures);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Recipients getRecipientsFor(String address) {
|
private Recipients getRecipientsFor(String address) {
|
||||||
|
@ -1036,6 +1080,30 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<IdentityKeyMismatch> 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<NetworkFailure> 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) {
|
private DisplayRecord.Body getBody(Cursor cursor) {
|
||||||
try {
|
try {
|
||||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
|
String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
|
||||||
|
|
|
@ -11,6 +11,7 @@ public interface MmsSmsColumns {
|
||||||
public static final String ADDRESS = "address";
|
public static final String ADDRESS = "address";
|
||||||
public static final String ADDRESS_DEVICE_ID = "address_device_id";
|
public static final String ADDRESS_DEVICE_ID = "address_device_id";
|
||||||
public static final String RECEIPT_COUNT = "delivery_receipt_count";
|
public static final String RECEIPT_COUNT = "delivery_receipt_count";
|
||||||
|
public static final String MISMATCHED_IDENTITIES = "mismatched_identities";
|
||||||
|
|
||||||
public static class Types {
|
public static class Types {
|
||||||
protected static final long TOTAL_MASK = 0xFFFFFFFF;
|
protected static final long TOTAL_MASK = 0xFFFFFFFF;
|
||||||
|
@ -37,6 +38,7 @@ public interface MmsSmsColumns {
|
||||||
protected static final long MESSAGE_FORCE_SMS_BIT = 0x40;
|
protected static final long MESSAGE_FORCE_SMS_BIT = 0x40;
|
||||||
|
|
||||||
// Key Exchange Information
|
// 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_BIT = 0x8000;
|
||||||
protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
|
protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
|
||||||
protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
|
protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
|
||||||
|
|
|
@ -23,8 +23,8 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.database.sqlite.SQLiteQueryBuilder;
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -49,13 +49,39 @@ public class MmsSmsDatabase extends Database {
|
||||||
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
|
||||||
|
MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||||
|
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||||
|
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||||
|
|
||||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
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);
|
setNotifyConverationListeners(cursor, threadId);
|
||||||
|
|
||||||
return cursor;
|
return cursor;
|
||||||
|
@ -71,12 +97,14 @@ public class MmsSmsDatabase extends Database {
|
||||||
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
|
||||||
|
MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||||
|
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||||
|
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||||
|
|
||||||
return queryTables(projection, selection, order, null, "1");
|
return queryTables(projection, selection, selection, order, null, "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getUnread() {
|
public Cursor getUnread() {
|
||||||
|
@ -89,12 +117,14 @@ public class MmsSmsDatabase extends Database {
|
||||||
MmsDatabase.PART_COUNT,
|
MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
|
||||||
|
MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||||
|
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||||
|
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||||
String selection = MmsSmsColumns.READ + " = 0";
|
String selection = MmsSmsColumns.READ + " = 0";
|
||||||
|
|
||||||
return queryTables(projection, selection, order, null, null);
|
return queryTables(projection, selection, selection, order, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getConversationCount(long threadId) {
|
public int getConversationCount(long threadId) {
|
||||||
|
@ -109,7 +139,7 @@ public class MmsSmsDatabase extends Database {
|
||||||
DatabaseFactory.getMmsDatabase(context).incrementDeliveryReceiptCount(address, timestamp);
|
DatabaseFactory.getMmsDatabase(context).incrementDeliveryReceiptCount(address, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cursor queryTables(String[] projection, String selection, String order, String groupBy, String limit) {
|
private Cursor queryTables(String[] projection, String smsSelection, String mmsSelection, String order, String groupBy, String limit) {
|
||||||
String[] mmsProjection = {MmsDatabase.DATE_SENT + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
String[] mmsProjection = {MmsDatabase.DATE_SENT + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||||
MmsDatabase.DATE_RECEIVED + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
MmsDatabase.DATE_RECEIVED + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||||
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
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.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||||
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,
|
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||||
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||||
|
@ -126,7 +157,8 @@ public class MmsSmsDatabase extends Database {
|
||||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||||
MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||||
|
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||||
|
|
||||||
|
|
||||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||||
|
@ -146,6 +178,7 @@ public class MmsSmsDatabase extends Database {
|
||||||
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
|
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
|
||||||
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
||||||
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
||||||
|
mmsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
|
||||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
|
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
|
||||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
|
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
|
||||||
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
|
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
|
||||||
|
@ -156,6 +189,7 @@ public class MmsSmsDatabase extends Database {
|
||||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_SIZE);
|
mmsColumnsPresent.add(MmsDatabase.MESSAGE_SIZE);
|
||||||
mmsColumnsPresent.add(MmsDatabase.EXPIRY);
|
mmsColumnsPresent.add(MmsDatabase.EXPIRY);
|
||||||
mmsColumnsPresent.add(MmsDatabase.STATUS);
|
mmsColumnsPresent.add(MmsDatabase.STATUS);
|
||||||
|
mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE);
|
||||||
|
|
||||||
Set<String> smsColumnsPresent = new HashSet<String>();
|
Set<String> smsColumnsPresent = new HashSet<String>();
|
||||||
smsColumnsPresent.add(MmsSmsColumns.ID);
|
smsColumnsPresent.add(MmsSmsColumns.ID);
|
||||||
|
@ -165,14 +199,15 @@ public class MmsSmsDatabase extends Database {
|
||||||
smsColumnsPresent.add(MmsSmsColumns.READ);
|
smsColumnsPresent.add(MmsSmsColumns.READ);
|
||||||
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
|
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
|
||||||
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
||||||
|
smsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
|
||||||
smsColumnsPresent.add(SmsDatabase.TYPE);
|
smsColumnsPresent.add(SmsDatabase.TYPE);
|
||||||
smsColumnsPresent.add(SmsDatabase.SUBJECT);
|
smsColumnsPresent.add(SmsDatabase.SUBJECT);
|
||||||
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
|
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
|
||||||
smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED);
|
smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED);
|
||||||
smsColumnsPresent.add(SmsDatabase.STATUS);
|
smsColumnsPresent.add(SmsDatabase.STATUS);
|
||||||
|
|
||||||
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 2, MMS_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, selection, null, null, null);
|
String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(TRANSPORT, smsProjection, smsColumnsPresent, 2, SMS_TRANSPORT, smsSelection, null, null, null);
|
||||||
|
|
||||||
SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
|
||||||
String unionQuery = unionQueryBuilder.buildUnionQuery(new String[] {smsSubQuery, mmsSubQuery}, order, null);
|
String unionQuery = unionQueryBuilder.buildUnionQuery(new String[] {smsSubQuery, mmsSubQuery}, order, null);
|
||||||
|
|
|
@ -28,6 +28,8 @@ import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
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.DisplayRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
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.IncomingKeyExchangeMessage;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
import org.whispersystems.jobqueue.JobManager;
|
import org.whispersystems.jobqueue.JobManager;
|
||||||
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
|
import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
|
||||||
|
@ -52,7 +58,9 @@ import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
|
||||||
* @author Moxie Marlinspike
|
* @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 TABLE_NAME = "sms";
|
||||||
public static final String PERSON = "person";
|
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, " +
|
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
|
||||||
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
|
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
|
||||||
RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
|
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 = {
|
public static final String[] CREATE_INDEXS = {
|
||||||
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||||
|
@ -85,7 +93,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
|
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
|
||||||
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
|
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
|
||||||
PROTOCOL, READ, STATUS, TYPE,
|
PROTOCOL, READ, STATUS, TYPE,
|
||||||
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT
|
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT,
|
||||||
|
MISMATCHED_IDENTITIES
|
||||||
};
|
};
|
||||||
|
|
||||||
private final JobManager jobManager;
|
private final JobManager jobManager;
|
||||||
|
@ -95,6 +104,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
|
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getTableName() {
|
||||||
|
return TABLE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
private void updateTypeBitmask(long id, long maskOff, long maskOn) {
|
private void updateTypeBitmask(long id, long maskOff, long maskOn) {
|
||||||
Log.w("MessageDatabase", "Updating ID: " + id + " to base type: " + maskOn);
|
Log.w("MessageDatabase", "Updating ID: " + id + " to base type: " + maskOn);
|
||||||
|
|
||||||
|
@ -162,6 +175,14 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
return 0;
|
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) {
|
public void markAsStaleKeyExchange(long id) {
|
||||||
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_STALE_BIT);
|
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) {
|
protected void updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
|
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
|
||||||
TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
|
TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
|
||||||
"WHERE " + ID + " = ?",
|
"WHERE " + ID + " = ?",
|
||||||
new String[] {body, messageId+""});
|
new String[] {body, messageId + ""});
|
||||||
|
|
||||||
long threadId = getThreadIdForMessage(messageId);
|
long threadId = getThreadIdForMessage(messageId);
|
||||||
|
|
||||||
|
@ -487,9 +508,11 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getMessage(long messageId) {
|
public Cursor getMessage(long messageId) {
|
||||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||||
return db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId+""},
|
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[]{messageId + ""},
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
|
||||||
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteMessage(long messageId) {
|
public void deleteMessage(long messageId) {
|
||||||
|
@ -516,7 +539,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
|
|
||||||
where += (" ELSE " + DATE_RECEIVED + " < " + date + " END)");
|
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<Long> threadIds) {
|
/*package*/ void deleteThreads(Set<Long> threadIds) {
|
||||||
|
@ -606,14 +629,17 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
|
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
|
||||||
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
|
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
|
||||||
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT));
|
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT));
|
||||||
Recipients recipients = getRecipientsFor(address);
|
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES));
|
||||||
DisplayRecord.Body body = getBody(cursor);
|
|
||||||
|
|
||||||
return new SmsMessageRecord(context, messageId, body, recipients,
|
List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument);
|
||||||
recipients.getPrimaryRecipient(),
|
Recipients recipients = getRecipientsFor(address);
|
||||||
addressDeviceId,
|
DisplayRecord.Body body = getBody(cursor);
|
||||||
dateSent, dateReceived, receiptCount, type,
|
|
||||||
threadId, status);
|
return new SmsMessageRecord(context, messageId, body, recipients,
|
||||||
|
recipients.getPrimaryRecipient(),
|
||||||
|
addressDeviceId,
|
||||||
|
dateSent, dateReceived, receiptCount, type,
|
||||||
|
threadId, status, mismatches);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Recipients getRecipientsFor(String address) {
|
private Recipients getRecipientsFor(String address) {
|
||||||
|
@ -631,6 +657,18 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<IdentityKeyMismatch> 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) {
|
protected DisplayRecord.Body getBody(Cursor cursor) {
|
||||||
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
|
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
|
||||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.thoughtcrime.securesms.database.documents;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface Document<T> {
|
||||||
|
|
||||||
|
public int size();
|
||||||
|
public List<T> getList();
|
||||||
|
|
||||||
|
}
|
|
@ -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<IdentityKey> {
|
||||||
|
@Override
|
||||||
|
public void serialize(IdentityKey value, JsonGenerator jsonGenerator, SerializerProvider serializers)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
jsonGenerator.writeString(Base64.encodeBytes(value.serialize()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class IdentityKeyDeserializer extends JsonDeserializer<IdentityKey> {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<IdentityKeyMismatch> {
|
||||||
|
|
||||||
|
@JsonProperty(value = "m")
|
||||||
|
private List<IdentityKeyMismatch> mismatches;
|
||||||
|
|
||||||
|
public IdentityKeyMismatchList() {
|
||||||
|
this.mismatches = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IdentityKeyMismatchList(List<IdentityKeyMismatch> mismatches) {
|
||||||
|
this.mismatches = mismatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
if (mismatches == null) return 0;
|
||||||
|
else return mismatches.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IdentityKeyMismatch> getList() {
|
||||||
|
return mismatches;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<NetworkFailure> {
|
||||||
|
|
||||||
|
@JsonProperty(value = "l")
|
||||||
|
private List<NetworkFailure> failures;
|
||||||
|
|
||||||
|
public NetworkFailureList() {
|
||||||
|
this.failures = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkFailureList(List<NetworkFailure> failures) {
|
||||||
|
this.failures = failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
if (failures == null) return 0;
|
||||||
|
else return failures.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
|
public List<NetworkFailure> getList() {
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
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.MediaNotFoundException;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
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.FutureTaskListener;
|
||||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,10 +55,13 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
||||||
long dateSent, long dateReceived, int deliveredCount,
|
long dateSent, long dateReceived, int deliveredCount,
|
||||||
long threadId, Body body,
|
long threadId, Body body,
|
||||||
ListenableFutureTask<SlideDeck> slideDeck,
|
ListenableFutureTask<SlideDeck> slideDeck,
|
||||||
int partCount, long mailbox)
|
int partCount, long mailbox,
|
||||||
|
List<IdentityKeyMismatch> mismatches,
|
||||||
|
List<NetworkFailure> failures)
|
||||||
{
|
{
|
||||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
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.context = context.getApplicationContext();
|
||||||
this.partCount = partCount;
|
this.partCount = partCount;
|
||||||
|
|
|
@ -19,18 +19,20 @@ package org.thoughtcrime.securesms.database.model;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
import android.text.style.RelativeSizeSpan;
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.text.style.TextAppearanceSpan;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
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.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for message record models that are displayed in
|
* The base class for message record models that are displayed in
|
||||||
* conversations, as opposed to models that are displayed in a thread list.
|
* 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 static final int MAX_DISPLAY_LENGTH = 2000;
|
||||||
|
|
||||||
private final Recipient individualRecipient;
|
private final Recipient individualRecipient;
|
||||||
private final int recipientDeviceId;
|
private final int recipientDeviceId;
|
||||||
private final long id;
|
private final long id;
|
||||||
private final int deliveryStatus;
|
private final int deliveryStatus;
|
||||||
private final int receiptCount;
|
private final int receiptCount;
|
||||||
|
private final List<IdentityKeyMismatch> mismatches;
|
||||||
|
private final List<NetworkFailure> networkFailures;
|
||||||
|
|
||||||
MessageRecord(Context context, long id, Body body, Recipients recipients,
|
MessageRecord(Context context, long id, Body body, Recipients recipients,
|
||||||
Recipient individualRecipient, int recipientDeviceId,
|
Recipient individualRecipient, int recipientDeviceId,
|
||||||
long dateSent, long dateReceived, long threadId,
|
long dateSent, long dateReceived, long threadId,
|
||||||
int deliveryStatus, int receiptCount, long type)
|
int deliveryStatus, int receiptCount, long type,
|
||||||
|
List<IdentityKeyMismatch> mismatches,
|
||||||
|
List<NetworkFailure> networkFailures)
|
||||||
{
|
{
|
||||||
super(context, body, recipients, dateSent, dateReceived, threadId, type);
|
super(context, body, recipients, dateSent, dateReceived, threadId, type);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
@ -65,6 +71,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
this.recipientDeviceId = recipientDeviceId;
|
this.recipientDeviceId = recipientDeviceId;
|
||||||
this.deliveryStatus = deliveryStatus;
|
this.deliveryStatus = deliveryStatus;
|
||||||
this.receiptCount = receiptCount;
|
this.receiptCount = receiptCount;
|
||||||
|
this.mismatches = mismatches;
|
||||||
|
this.networkFailures = networkFailures;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean isMms();
|
public abstract boolean isMms();
|
||||||
|
@ -145,6 +153,10 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
return SmsDatabase.Types.isPendingSmsFallbackType(type);
|
return SmsDatabase.Types.isPendingSmsFallbackType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isIdentityMismatchFailure() {
|
||||||
|
return mismatches != null && !mismatches.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isPendingSecureSmsFallback() {
|
public boolean isPendingSecureSmsFallback() {
|
||||||
return SmsDatabase.Types.isPendingSecureSmsFallbackType(type);
|
return SmsDatabase.Types.isPendingSecureSmsFallbackType(type);
|
||||||
}
|
}
|
||||||
|
@ -181,6 +193,18 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<IdentityKeyMismatch> getIdentityKeyMismatches() {
|
||||||
|
return mismatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NetworkFailure> getNetworkFailures() {
|
||||||
|
return networkFailures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNetworkFailures() {
|
||||||
|
return networkFailures != null && !networkFailures.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
protected SpannableString emphasisAdded(String sequence) {
|
protected SpannableString emphasisAdded(String sequence) {
|
||||||
SpannableString spannable = new SpannableString(sequence);
|
SpannableString spannable = new SpannableString(sequence);
|
||||||
spannable.setSpan(new RelativeSizeSpan(0.9f), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
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() {
|
public int hashCode() {
|
||||||
return (int)getId();
|
return (int)getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,13 @@ import android.text.SpannableString;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
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.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the message record model for MMS messages that are
|
* Represents the message record model for MMS messages that are
|
||||||
* notifications (ie: they're pointers to undownloaded media).
|
* 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)
|
long expiry, int status, byte[] transactionId, long mailbox)
|
||||||
{
|
{
|
||||||
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
|
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
|
||||||
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, receiptCount, mailbox);
|
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, receiptCount, mailbox,
|
||||||
|
new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>());
|
||||||
|
|
||||||
this.contentLocation = contentLocation;
|
this.contentLocation = contentLocation;
|
||||||
this.messageSize = messageSize;
|
this.messageSize = messageSize;
|
||||||
|
|
|
@ -23,10 +23,15 @@ import android.text.SpannableString;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
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.protocol.Tag;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message record model which represents standard SMS messages.
|
* The message record model which represents standard SMS messages.
|
||||||
*
|
*
|
||||||
|
@ -43,10 +48,11 @@ public class SmsMessageRecord extends MessageRecord {
|
||||||
long dateSent, long dateReceived,
|
long dateSent, long dateReceived,
|
||||||
int receiptCount,
|
int receiptCount,
|
||||||
long type, long threadId,
|
long type, long threadId,
|
||||||
int status)
|
int status, List<IdentityKeyMismatch> mismatches)
|
||||||
{
|
{
|
||||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
||||||
dateSent, dateReceived, threadId, receiptCount, getGenericDeliveryStatus(status), type);
|
dateSent, dateReceived, threadId, receiptCount, getGenericDeliveryStatus(status), type,
|
||||||
|
mismatches, new LinkedList<NetworkFailure>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getType() {
|
public long getType() {
|
||||||
|
@ -55,7 +61,9 @@ public class SmsMessageRecord extends MessageRecord {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpannableString getDisplayBody() {
|
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("");
|
return new SpannableString("");
|
||||||
} else if (isStaleKeyExchange()) {
|
} else if (isStaleKeyExchange()) {
|
||||||
return emphasisAdded(context.getString(R.string.ConversationItem_error_received_stale_key_exchange_message));
|
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("");
|
return new SpannableString("");
|
||||||
} else if (isKeyExchange() && !isOutgoing()) {
|
} else if (isKeyExchange() && !isOutgoing()) {
|
||||||
return emphasisAdded(context.getString(R.string.ConversationItem_received_key_exchange_message_click_to_process));
|
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)) {
|
} else if (SmsDatabase.Types.isDuplicateMessageType(type)) {
|
||||||
return emphasisAdded(context.getString(R.string.SmsMessageRecord_duplicate_message));
|
return emphasisAdded(context.getString(R.string.SmsMessageRecord_duplicate_message));
|
||||||
} else if (SmsDatabase.Types.isDecryptInProgressType(type)) {
|
} else if (SmsDatabase.Types.isDecryptInProgressType(type)) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.jobqueue.JobParameters;
|
import org.whispersystems.jobqueue.JobParameters;
|
||||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyIdException;
|
import org.whispersystems.libaxolotl.InvalidKeyIdException;
|
||||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||||
|
@ -37,13 +38,14 @@ import org.whispersystems.libaxolotl.InvalidVersionException;
|
||||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||||
import org.whispersystems.libaxolotl.NoSessionException;
|
import org.whispersystems.libaxolotl.NoSessionException;
|
||||||
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
||||||
|
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
||||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||||
import org.whispersystems.libaxolotl.state.SessionStore;
|
import org.whispersystems.libaxolotl.state.SessionStore;
|
||||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
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.TextSecureEnvelope;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
||||||
import org.whispersystems.textsecure.api.crypto.TextSecureCipher;
|
|
||||||
|
|
||||||
import ws.com.google.android.mms.MmsException;
|
import ws.com.google.android.mms.MmsException;
|
||||||
|
|
||||||
|
@ -52,14 +54,20 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||||
public static final String TAG = PushDecryptJob.class.getSimpleName();
|
public static final String TAG = PushDecryptJob.class.getSimpleName();
|
||||||
|
|
||||||
private final long messageId;
|
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()
|
super(context, JobParameters.newBuilder()
|
||||||
.withPersistence()
|
.withPersistence()
|
||||||
.withRequirement(new MasterSecretRequirement(context))
|
.withRequirement(new MasterSecretRequirement(context))
|
||||||
.withGroupId(sender)
|
.withGroupId(sender)
|
||||||
.create());
|
.create());
|
||||||
this.messageId = messageId;
|
this.messageId = pushMessageId;
|
||||||
|
this.smsMessageId = smsMessageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,7 +82,7 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||||
PushDatabase database = DatabaseFactory.getPushDatabase(context);
|
PushDatabase database = DatabaseFactory.getPushDatabase(context);
|
||||||
TextSecureEnvelope envelope = database.get(messageId);
|
TextSecureEnvelope envelope = database.get(messageId);
|
||||||
|
|
||||||
handleMessage(masterSecret, envelope);
|
handleMessage(masterSecret, envelope, smsMessageId);
|
||||||
database.delete(messageId);
|
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 {
|
try {
|
||||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
|
Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
|
||||||
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
|
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
|
||||||
|
@ -98,59 +106,72 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||||
|
|
||||||
TextSecureMessage message = cipher.decrypt(envelope);
|
TextSecureMessage message = cipher.decrypt(envelope);
|
||||||
|
|
||||||
if (message.isEndSession()) handleEndSessionMessage(masterSecret, recipientId, envelope, message);
|
if (message.isEndSession()) handleEndSessionMessage(masterSecret, recipientId, envelope, message, smsMessageId);
|
||||||
else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, envelope, message);
|
else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, envelope, message, smsMessageId);
|
||||||
else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, envelope, message);
|
else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, envelope, message, smsMessageId);
|
||||||
else handleTextMessage(masterSecret, envelope, message);
|
else handleTextMessage(masterSecret, envelope, message, smsMessageId);
|
||||||
|
|
||||||
if (envelope.isPreKeyWhisperMessage()) {
|
if (envelope.isPreKeyWhisperMessage()) {
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
|
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
|
||||||
}
|
}
|
||||||
} catch (InvalidVersionException e) {
|
} catch (InvalidVersionException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleInvalidVersionMessage(masterSecret, envelope);
|
handleInvalidVersionMessage(masterSecret, envelope, smsMessageId);
|
||||||
} catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException | RecipientFormattingException e) {
|
} catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException | RecipientFormattingException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleCorruptMessage(masterSecret, envelope);
|
handleCorruptMessage(masterSecret, envelope, smsMessageId);
|
||||||
} catch (NoSessionException e) {
|
} catch (NoSessionException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleNoSessionMessage(masterSecret, envelope);
|
handleNoSessionMessage(masterSecret, envelope, smsMessageId);
|
||||||
} catch (LegacyMessageException e) {
|
} catch (LegacyMessageException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleLegacyMessage(masterSecret, envelope);
|
handleLegacyMessage(masterSecret, envelope, smsMessageId);
|
||||||
} catch (DuplicateMessageException e) {
|
} catch (DuplicateMessageException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleDuplicateMessage(masterSecret, envelope);
|
handleDuplicateMessage(masterSecret, envelope, smsMessageId);
|
||||||
} catch (UntrustedIdentityException e) {
|
} catch (UntrustedIdentityException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleUntrustedIdentityMessage(masterSecret, envelope);
|
handleUntrustedIdentityMessage(masterSecret, envelope, smsMessageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleEndSessionMessage(MasterSecret masterSecret, long recipientId,
|
private void handleEndSessionMessage(MasterSecret masterSecret, long recipientId,
|
||||||
TextSecureEnvelope envelope, TextSecureMessage message)
|
TextSecureEnvelope envelope, TextSecureMessage message,
|
||||||
|
long smsMessageId)
|
||||||
{
|
{
|
||||||
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(),
|
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||||
envelope.getSourceDevice(),
|
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(),
|
||||||
message.getTimestamp(),
|
envelope.getSourceDevice(),
|
||||||
"", Optional.<TextSecureGroup>absent());
|
message.getTimestamp(),
|
||||||
|
"", Optional.<TextSecureGroup>absent());
|
||||||
|
|
||||||
IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage);
|
long threadId;
|
||||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
|
||||||
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(masterSecret, incomingEndSessionMessage);
|
if (smsMessageId <= 0) {
|
||||||
|
IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage);
|
||||||
|
Pair<Long, Long> messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage);
|
||||||
|
threadId = messageAndThreadId.second;
|
||||||
|
} else {
|
||||||
|
smsDatabase.markAsEndSession(smsMessageId);
|
||||||
|
threadId = smsDatabase.getThreadIdForMessage(smsMessageId);
|
||||||
|
}
|
||||||
|
|
||||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||||
sessionStore.deleteAllSessions(recipientId);
|
sessionStore.deleteAllSessions(recipientId);
|
||||||
|
|
||||||
SecurityEvent.broadcastSecurityUpdateEvent(context, messageAndThreadId.second);
|
SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
|
||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
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);
|
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
|
throws MmsException
|
||||||
{
|
{
|
||||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||||
|
@ -174,73 +195,123 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||||
.getJobManager()
|
.getJobManager()
|
||||||
.add(new AttachmentDownloadJob(context, messageAndThreadId.first));
|
.add(new AttachmentDownloadJob(context, messageAndThreadId.first));
|
||||||
|
|
||||||
|
if (smsMessageId >= 0) {
|
||||||
|
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId);
|
||||||
|
}
|
||||||
|
|
||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTextMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message) {
|
private void handleTextMessage(MasterSecret masterSecret, TextSecureEnvelope envelope,
|
||||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
TextSecureMessage message, long smsMessageId)
|
||||||
String body = message.getBody().isPresent() ? message.getBody().get() : "";
|
{
|
||||||
IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(),
|
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(),
|
envelope.getSourceDevice(),
|
||||||
message.getTimestamp(), body,
|
message.getTimestamp(), body,
|
||||||
message.getGroupInfo());
|
message.getGroupInfo());
|
||||||
|
|
||||||
if (message.isSecure()) {
|
if (message.isSecure()) {
|
||||||
textMessage = new IncomingEncryptedMessage(textMessage, body);
|
textMessage = new IncomingEncryptedMessage(textMessage, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage);
|
||||||
|
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage);
|
|
||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope) {
|
private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
|
||||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageAndThreadId.first);
|
|
||||||
|
|
||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
if (smsMessageId <= 0) {
|
||||||
|
Pair<Long, Long> 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) {
|
private void handleCorruptMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
|
||||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptFailed(messageAndThreadId.first);
|
|
||||||
|
|
||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
if (smsMessageId <= 0) {
|
||||||
|
Pair<Long, Long> 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) {
|
private void handleNoSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
|
||||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsNoSession(messageAndThreadId.first);
|
|
||||||
|
|
||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
if (smsMessageId <= 0) {
|
||||||
|
Pair<Long, Long> 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) {
|
private void handleLegacyMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
|
||||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsLegacyVersion(messageAndThreadId.first);
|
|
||||||
|
|
||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
if (smsMessageId <= 0) {
|
||||||
|
Pair<Long, Long> 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) {
|
private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
|
||||||
// Let's start ignoring these now.
|
// Let's start ignoring these now
|
||||||
|
// SmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||||
// Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
|
||||||
// DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptDuplicate(messageAndThreadId.first);
|
|
||||||
//
|
//
|
||||||
// MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
// if (smsMessageId <= 0) {
|
||||||
|
// Pair<Long, Long> 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) {
|
private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) {
|
||||||
String encoded = Base64.encodeBytes(envelope.getMessage());
|
try {
|
||||||
IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(),
|
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||||
envelope.getTimestamp(), encoded,
|
Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
|
||||||
Optional.<TextSecureGroup>absent());
|
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.<TextSecureGroup>absent());
|
||||||
|
|
||||||
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
|
if (smsMessageId <= 0) {
|
||||||
Pair<Long, Long> messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context)
|
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
|
||||||
.insertMessageInbox(masterSecret, bundleMessage);
|
Pair<Long, Long> 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<Long, Long> insertPlaceholder(MasterSecret masterSecret, TextSecureEnvelope envelope) {
|
private Pair<Long, Long> insertPlaceholder(MasterSecret masterSecret, TextSecureEnvelope envelope) {
|
||||||
|
|
|
@ -8,13 +8,14 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||||
|
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||||
import org.thoughtcrime.securesms.mms.PartParser;
|
import org.thoughtcrime.securesms.mms.PartParser;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
import org.whispersystems.jobqueue.JobParameters;
|
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.TextSecureGroup;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
||||||
import org.whispersystems.textsecure.api.push.PushAddress;
|
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.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.api.util.InvalidNumberException;
|
||||||
|
import org.whispersystems.textsecure.internal.push.PushMessageProtos;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -47,8 +50,9 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||||
@Inject transient TextSecureMessageSenderFactory messageSenderFactory;
|
@Inject transient TextSecureMessageSenderFactory messageSenderFactory;
|
||||||
|
|
||||||
private final long messageId;
|
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()
|
super(context, JobParameters.newBuilder()
|
||||||
.withPersistence()
|
.withPersistence()
|
||||||
.withGroupId(destination)
|
.withGroupId(destination)
|
||||||
|
@ -57,21 +61,25 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||||
.withRetryCount(5)
|
.withRetryCount(5)
|
||||||
.create());
|
.create());
|
||||||
|
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
|
this.filterRecipientId = filterRecipientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAdded() {
|
public void onAdded() {
|
||||||
|
DatabaseFactory.getMmsDatabase(context)
|
||||||
|
.markAsSending(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||||
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
|
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
deliver(masterSecret, message);
|
deliver(masterSecret, message, filterRecipientId);
|
||||||
|
|
||||||
database.markAsPush(messageId);
|
database.markAsPush(messageId);
|
||||||
database.markAsSecure(messageId);
|
database.markAsSecure(messageId);
|
||||||
|
@ -82,16 +90,27 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||||
} catch (EncapsulatedExceptions e) {
|
} catch (EncapsulatedExceptions e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
if (!e.getUnregisteredUserExceptions().isEmpty()) {
|
List<NetworkFailure> failures = new LinkedList<>();
|
||||||
database.markAsSentFailed(messageId);
|
|
||||||
|
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()) {
|
for (UntrustedIdentityException uie : e.getUntrustedIdentityExceptions()) {
|
||||||
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey());
|
Recipient recipient = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false).getPrimaryRecipient();
|
||||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage);
|
database.addMismatchedIdentity(messageId, recipient.getRecipientId(), uie.getIdentityKey());
|
||||||
database.markAsSentFailed(messageId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database.addFailures(messageId, failures);
|
||||||
|
database.markAsSentFailed(messageId);
|
||||||
|
database.markAsPush(messageId);
|
||||||
|
|
||||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,14 +126,17 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||||
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
|
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
|
throws IOException, RecipientFormattingException, InvalidNumberException, EncapsulatedExceptions
|
||||||
{
|
{
|
||||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||||
byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString());
|
byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString());
|
||||||
Recipients recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
|
Recipients recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
|
||||||
List<PushAddress> addresses = getPushAddresses(recipients);
|
|
||||||
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
|
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
|
||||||
|
List<PushAddress> addresses;
|
||||||
|
|
||||||
|
if (filterRecipientId >= 0) addresses = getPushAddresses(filterRecipientId);
|
||||||
|
else addresses = getPushAddresses(recipients);
|
||||||
|
|
||||||
if (MmsSmsColumns.Types.isGroupUpdate(message.getDatabaseMessageBox()) ||
|
if (MmsSmsColumns.Types.isGroupUpdate(message.getDatabaseMessageBox()) ||
|
||||||
MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox()))
|
MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox()))
|
||||||
|
@ -149,4 +171,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||||
return addresses;
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<PushAddress> getPushAddresses(long filterRecipientId) throws InvalidNumberException {
|
||||||
|
List<PushAddress> addresses = new LinkedList<>();
|
||||||
|
addresses.add(getPushAddress(RecipientFactory.getRecipientForId(context, filterRecipientId, false)));
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||||
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
import org.thoughtcrime.securesms.mms.PartParser;
|
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.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
|
|
||||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||||
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
|
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
|
||||||
|
@ -55,12 +55,15 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAdded() {
|
public void onAdded() {
|
||||||
|
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||||
|
mmsDatabase.markAsSending(messageId);
|
||||||
|
mmsDatabase.markAsPush(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSend(MasterSecret masterSecret)
|
public void onSend(MasterSecret masterSecret)
|
||||||
throws RetryLaterException, MmsException, NoSuchMessageException, UndeliverableMessageException
|
throws RetryLaterException, MmsException, NoSuchMessageException,
|
||||||
|
UndeliverableMessageException, RecipientFormattingException
|
||||||
{
|
{
|
||||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||||
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
|
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
|
||||||
|
@ -80,9 +83,13 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||||
database.markAsPendingSecureSmsFallback(messageId);
|
database.markAsPendingSecureSmsFallback(messageId);
|
||||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||||
} catch (UntrustedIdentityException uie) {
|
} catch (UntrustedIdentityException uie) {
|
||||||
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey());
|
Log.w(TAG, uie);
|
||||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage);
|
Recipients recipients = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false);
|
||||||
|
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
|
||||||
|
|
||||||
|
database.addMismatchedIdentity(messageId, recipientId, uie.getIdentityKey());
|
||||||
database.markAsSentFailed(messageId);
|
database.markAsSentFailed(messageId);
|
||||||
|
database.markAsPush(messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,14 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||||
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
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.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
|
|
||||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||||
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
|
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
|
||||||
|
@ -47,14 +49,16 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAdded() {
|
public void onAdded() {
|
||||||
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||||
|
smsDatabase.markAsSending(messageId);
|
||||||
|
smsDatabase.markAsPush(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
|
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException, RecipientFormattingException {
|
||||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||||
String destination = record.getIndividualRecipient().getNumber();
|
String destination = record.getIndividualRecipient().getNumber();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Log.w(TAG, "Sending message: " + messageId);
|
Log.w(TAG, "Sending message: " + messageId);
|
||||||
|
@ -74,9 +78,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||||
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
|
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
|
||||||
} catch (UntrustedIdentityException e) {
|
} catch (UntrustedIdentityException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(e.getE164Number(), e.getIdentityKey());
|
Recipients recipients = RecipientFactory.getRecipientsFromString(context, e.getE164Number(), false);
|
||||||
database.insertMessageInbox(masterSecret, identityUpdateMessage);
|
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
|
||||||
|
|
||||||
|
database.addMismatchedIdentity(record.getId(), recipientId, e.getIdentityKey());
|
||||||
database.markAsSentFailed(record.getId());
|
database.markAsSentFailed(record.getId());
|
||||||
|
database.markAsPush(record.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Recipients {
|
public class Recipients implements Iterable<Recipient> {
|
||||||
|
|
||||||
private List<Recipient> recipients;
|
private List<Recipient> recipients;
|
||||||
|
|
||||||
|
@ -165,4 +165,9 @@ public class Recipients {
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Recipient> iterator() {
|
||||||
|
return recipients.iterator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
public static void resend(Context context, MasterSecret masterSecret, MessageRecord messageRecord) {
|
||||||
try {
|
try {
|
||||||
Recipients recipients = messageRecord.getRecipients();
|
|
||||||
long messageId = messageRecord.getId();
|
long messageId = messageRecord.getId();
|
||||||
boolean forceSms = messageRecord.isForcedSms();
|
boolean forceSms = messageRecord.isForcedSms();
|
||||||
boolean keyExchange = messageRecord.isKeyExchange();
|
boolean keyExchange = messageRecord.isKeyExchange();
|
||||||
|
|
||||||
if (messageRecord.isMms()) {
|
if (messageRecord.isMms()) {
|
||||||
|
Recipients recipients = DatabaseFactory.getMmsAddressDatabase(context).getRecipientsForId(messageId);
|
||||||
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);
|
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);
|
||||||
} else {
|
} else {
|
||||||
|
Recipients recipients = messageRecord.getRecipients();
|
||||||
sendTextMessage(context, recipients, forceSms, keyExchange, messageId);
|
sendTextMessage(context, recipients, forceSms, keyExchange, messageId);
|
||||||
}
|
}
|
||||||
} catch (MmsException e) {
|
} catch (MmsException e) {
|
||||||
|
@ -135,7 +143,7 @@ public class MessageSender {
|
||||||
if (!forceSms && isSelfSend(context, recipients)) {
|
if (!forceSms && isSelfSend(context, recipients)) {
|
||||||
sendMediaSelf(context, masterSecret, messageId);
|
sendMediaSelf(context, masterSecret, messageId);
|
||||||
} else if (isGroupPushSend(recipients)) {
|
} else if (isGroupPushSend(recipients)) {
|
||||||
sendGroupPush(context, recipients, messageId);
|
sendGroupPush(context, recipients, messageId, -1);
|
||||||
} else if (!forceSms && isPushMediaSend(context, recipients)) {
|
} else if (!forceSms && isPushMediaSend(context, recipients)) {
|
||||||
sendMediaPush(context, recipients, messageId);
|
sendMediaPush(context, recipients, messageId);
|
||||||
} else {
|
} else {
|
||||||
|
@ -186,9 +194,9 @@ public class MessageSender {
|
||||||
jobManager.add(new PushMediaSendJob(context, messageId, recipients.getPrimaryRecipient().getNumber()));
|
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 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) {
|
private static void sendSms(Context context, Recipients recipients, long messageId) {
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
package org.thoughtcrime.securesms.util;
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
|
@ -71,4 +75,17 @@ public class DateUtils extends android.text.format.DateUtils {
|
||||||
return DateUtils.formatDateTime(c, timestamp, formatFlags);
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,12 @@ import android.util.Pair;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.google.thoughtcrimegson.Gson;
|
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||||
import com.google.thoughtcrimegson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
@ -35,6 +33,8 @@ import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class Emoji {
|
public class Emoji {
|
||||||
|
|
||||||
|
private static final String TAG = Emoji.class.getSimpleName();
|
||||||
|
|
||||||
private static ExecutorService executor = Util.newSingleThreadedLifoExecutor();
|
private static ExecutorService executor = Util.newSingleThreadedLifoExecutor();
|
||||||
|
|
||||||
public static final int[][] PAGES = {
|
public static final int[][] PAGES = {
|
||||||
|
@ -303,9 +303,16 @@ public class Emoji {
|
||||||
}
|
}
|
||||||
|
|
||||||
String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]");
|
String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]");
|
||||||
Type type = new TypeToken<LinkedHashSet<String>>() {
|
|
||||||
}.getType();
|
try {
|
||||||
recentlyUsed = new Gson().fromJson(serialized, type);
|
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) {
|
public static String[] getRecentlyUsed(Context context) {
|
||||||
|
@ -337,10 +344,15 @@ public class Emoji {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
String serialized = new Gson().toJson(latestRecentlyUsed);
|
try {
|
||||||
prefs.edit()
|
String serialized = JsonUtils.toJson(latestRecentlyUsed);
|
||||||
.putString(EMOJI_LRU_PREFERENCE, serialized)
|
prefs.edit()
|
||||||
.apply();
|
.putString(EMOJI_LRU_PREFERENCE, serialized)
|
||||||
|
.apply();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
|
|
33
src/org/thoughtcrime/securesms/util/JsonUtils.java
Normal file
33
src/org/thoughtcrime/securesms/util/JsonUtils.java
Normal file
|
@ -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> T fromJson(String serialized, Class<T> clazz) throws IOException {
|
||||||
|
return objectMapper.readValue(serialized, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T fromJson(InputStreamReader serialized, Class<T> 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;
|
||||||
|
}
|
||||||
|
}
|
83
src/org/thoughtcrime/securesms/util/RecipientViewUtil.java
Normal file
83
src/org/thoughtcrime/securesms/util/RecipientViewUtil.java
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue