aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs/conversations
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu/siacs/conversations')
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java2
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java13
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java5
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java10
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java15
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java52
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java228
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java210
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java30
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java44
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java8
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java2
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java10
-rw-r--r--src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java14
-rw-r--r--src/main/java/eu/siacs/conversations/services/MessageArchiveService.java4
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java19
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java18
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java8
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java16
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java21
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/OmemoActivity.java20
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java56
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java16
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java62
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java10
26 files changed, 531 insertions, 364 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 1a992e48..48d2620a 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -109,7 +109,7 @@ public final class Config {
public static final int MAM_MAX_MESSAGES = 500;
public static final long FREQUENT_RESTARTS_DETECTION_WINDOW = 12 * 60 * 60 * 1000; // 10 hours
- public static final long FREQUENT_RESTARTS_THRESHOLD = 16;
+ public static final long FREQUENT_RESTARTS_THRESHOLD = 0; // previous value was 16;
public static final ChatState DEFAULT_CHATSTATE = ChatState.ACTIVE;
public static final int TYPING_TIMEOUT = 8;
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
index 65ac7b0a..6bde0fe7 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -439,8 +439,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
mXmppConnectionService.sendIqPacket(account, publish, null);
}
- public void purgeKey(final String fingerprint) {
- axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised());
+ public void distrustFingerprint(final String fingerprint) {
+ final String fp = fingerprint.replaceAll("\\s", "");
+ final FingerprintStatus fingerprintStatus = axolotlStore.getFingerprintStatus(fp);
+ axolotlStore.setFingerprintStatus(fp,fingerprintStatus.toUntrusted());
}
public void publishOwnDeviceIdIfNeeded() {
@@ -1120,7 +1122,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
session.resetPreKeyId();
}
} catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from "+message.getFrom()+": " + e.getMessage());
}
if (session.isFresh() && plaintextMessage != null) {
@@ -1134,7 +1136,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
XmppAxolotlSession session = getReceivingSession(message);
+ try {
keyTransportMessage = message.getParameters(session, getOwnDeviceId());
+ } catch (CryptoFailedException e) {
+ Log.d(Config.LOGTAG,"could not decrypt keyTransport message "+e.getMessage());
+ keyTransportMessage = null;
+ }
if (session.isFresh() && keyTransportMessage != null) {
putFreshSession(session);
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java
index 5796ef30..e549598c 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java
@@ -1,6 +1,11 @@
package eu.siacs.conversations.crypto.axolotl;
public class CryptoFailedException extends Exception {
+
+ public CryptoFailedException(String msg) {
+ super(msg);
+ }
+
public CryptoFailedException(Exception e){
super(e);
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
index 31b2264b..56f4a5d2 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
@@ -126,17 +126,17 @@ public class FingerprintStatus implements Comparable<FingerprintStatus> {
return trust;
}
- public static FingerprintStatus createCompromised() {
+ public FingerprintStatus toVerified() {
FingerprintStatus status = new FingerprintStatus();
- status.active = false;
- status.trust = Trust.COMPROMISED;
+ status.active = active;
+ status.trust = Trust.VERIFIED;
return status;
}
- public FingerprintStatus toVerified() {
+ public FingerprintStatus toUntrusted() {
FingerprintStatus status = new FingerprintStatus();
status.active = active;
- status.trust = Trust.VERIFIED;
+ status.trust = Trust.UNTRUSTED;
return status;
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
index 1f532370..cac298e0 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
@@ -23,7 +23,6 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import eu.siacs.conversations.Config;
-import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -251,16 +250,16 @@ public class XmppAxolotlMessage {
return encryptionElement;
}
- private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) {
+ private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
XmppAxolotlSession.AxolotlKey encryptedKey = keys.get(sourceDeviceId);
- return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null;
+ if (encryptedKey == null) {
+ throw new CryptoFailedException("Message was not encrypted for this device");
+ }
+ return session.processReceiving(encryptedKey);
}
- public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) {
- byte[] key = unpackKey(session, sourceDeviceId);
- return (key != null)
- ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV())
- : null;
+ public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
+ return new XmppAxolotlKeyTransportMessage(session.getFingerprint(), unpackKey(session, sourceDeviceId), getIV());
}
public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
index 773b6857..359cb7fd 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
@@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
+import org.bouncycastle.math.ec.PreCompInfo;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey;
@@ -18,9 +19,11 @@ import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
+import org.whispersystems.libaxolotl.util.guava.Optional;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.utils.CryptoHelper;
public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
private final SessionCipher cipher;
@@ -82,42 +85,43 @@ public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
}
@Nullable
- public byte[] processReceiving(AxolotlKey encryptedKey) {
- byte[] plaintext = null;
+ public byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException {
+ byte[] plaintext;
FingerprintStatus status = getTrust();
if (!status.isCompromised()) {
try {
+ CiphertextMessage ciphertextMessage;
try {
- PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey.key);
- if (!message.getPreKeyId().isPresent()) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId");
- return null;
+ ciphertextMessage = new PreKeyWhisperMessage(encryptedKey.key);
+ Optional<Integer> optionalPreKeyId = ((PreKeyWhisperMessage) ciphertextMessage).getPreKeyId();
+ IdentityKey identityKey = ((PreKeyWhisperMessage) ciphertextMessage).getIdentityKey();
+ if (!optionalPreKeyId.isPresent()) {
+ throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
+ }
+ preKeyId = optionalPreKeyId.get();
+ if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
+ throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
+ }
+ this.identityKey = identityKey;
+ } catch (InvalidVersionException | InvalidMessageException e) {
+ ciphertextMessage = new WhisperMessage(encryptedKey.key);
}
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
- IdentityKey msgIdentityKey = message.getIdentityKey();
- if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
+ if (ciphertextMessage instanceof PreKeyWhisperMessage) {
+ plaintext = cipher.decrypt((PreKeyWhisperMessage) ciphertextMessage);
} else {
- this.identityKey = msgIdentityKey;
- plaintext = cipher.decrypt(message);
- preKeyId = message.getPreKeyId().get();
+ plaintext = cipher.decrypt((WhisperMessage) ciphertextMessage);
}
- } catch (InvalidMessageException | InvalidVersionException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
- WhisperMessage message = new WhisperMessage(encryptedKey.key);
- plaintext = cipher.decrypt(message);
- } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
+ } catch (InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException | InvalidKeyIdException | UntrustedIdentityException e) {
+ if (!(e instanceof DuplicateMessageException)) {
+ e.printStackTrace();
}
- } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
+ throw new CryptoFailedException("Error decrypting WhisperMessage " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
-
- if (plaintext != null) {
if (!status.isActive()) {
setTrust(status.toActive());
}
- }
+ } else {
+ throw new CryptoFailedException("not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised");
}
return plaintext;
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java
new file mode 100644
index 00000000..566835d6
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java
@@ -0,0 +1,228 @@
+package eu.siacs.conversations.crypto.sasl;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.Base64;
+import android.util.LruCache;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.security.SecureRandom;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.xml.TagWriter;
+
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+abstract class ScramMechanism extends SaslMechanism {
+ // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
+ private final static String GS2_HEADER = "n,,";
+ private String clientFirstMessageBare;
+ private final String clientNonce;
+ private byte[] serverSignature = null;
+ static HMac HMAC;
+ static Digest DIGEST;
+ private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
+ private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
+
+ private static class KeyPair {
+ final byte[] clientKey;
+ final byte[] serverKey;
+
+ KeyPair(final byte[] clientKey, final byte[] serverKey) {
+ this.clientKey = clientKey;
+ this.serverKey = serverKey;
+ }
+ }
+
+ static {
+ CACHE = new LruCache<String, KeyPair>(10) {
+ protected KeyPair create(final String k) {
+ // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
+ // Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()'
+ // is applied to prevent commas in the strings breaking things.
+ final String[] kparts = k.split(",", 4);
+ try {
+ final byte[] saltedPassword, serverKey, clientKey;
+ saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(),
+ Base64.decode(CryptoHelper.hexToString(kparts[2]), Base64.DEFAULT), Integer.valueOf(kparts[3]));
+ serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
+ clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
+
+ return new KeyPair(clientKey, serverKey);
+ } catch (final InvalidKeyException | NumberFormatException e) {
+ return null;
+ }
+ }
+ };
+ }
+
+ private static final LruCache<String, KeyPair> CACHE;
+
+ protected State state = State.INITIAL;
+
+ ScramMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+ super(tagWriter, account, rng);
+
+ // This nonce should be different for each authentication attempt.
+ clientNonce = new BigInteger(100, this.rng).toString(32);
+ clientFirstMessageBare = "";
+ }
+
+ @Override
+ public String getClientFirstMessage() {
+ if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
+ clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) +
+ ",r=" + this.clientNonce;
+ state = State.AUTH_TEXT_SENT;
+ }
+ return Base64.encodeToString(
+ (GS2_HEADER + clientFirstMessageBare).getBytes(Charset.defaultCharset()),
+ Base64.NO_WRAP);
+ }
+
+ @Override
+ public String getResponse(final String challenge) throws AuthenticationException {
+ switch (state) {
+ case AUTH_TEXT_SENT:
+ if (challenge == null) {
+ throw new AuthenticationException("challenge can not be null");
+ }
+ byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT);
+ final Tokenizer tokenizer = new Tokenizer(serverFirstMessage);
+ String nonce = "";
+ int iterationCount = -1;
+ String salt = "";
+ for (final String token : tokenizer) {
+ if (token.charAt(1) == '=') {
+ switch (token.charAt(0)) {
+ case 'i':
+ try {
+ iterationCount = Integer.parseInt(token.substring(2));
+ } catch (final NumberFormatException e) {
+ throw new AuthenticationException(e);
+ }
+ break;
+ case 's':
+ salt = token.substring(2);
+ break;
+ case 'r':
+ nonce = token.substring(2);
+ break;
+ case 'm':
+ /*
+ * RFC 5802:
+ * m: This attribute is reserved for future extensibility. In this
+ * version of SCRAM, its presence in a client or a server message
+ * MUST cause authentication failure when the attribute is parsed by
+ * the other end.
+ */
+ throw new AuthenticationException("Server sent reserved token: `m'");
+ }
+ }
+ }
+
+ if (iterationCount < 0) {
+ throw new AuthenticationException("Server did not send iteration count");
+ }
+ if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
+ throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce);
+ }
+ if (salt.isEmpty()) {
+ throw new AuthenticationException("Server sent empty salt");
+ }
+
+ final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString(
+ GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce;
+ final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
+ + clientFinalMessageWithoutProof).getBytes();
+
+ // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
+ final KeyPair keys = CACHE.get(
+ CryptoHelper.bytesToHex(account.getJid().toBareJid().toString().getBytes()) + ","
+ + CryptoHelper.bytesToHex(account.getPassword().getBytes()) + ","
+ + CryptoHelper.bytesToHex(salt.getBytes()) + ","
+ + String.valueOf(iterationCount)
+ );
+ if (keys == null) {
+ throw new AuthenticationException("Invalid keys generated");
+ }
+ final byte[] clientSignature;
+ try {
+ serverSignature = hmac(keys.serverKey, authMessage);
+ final byte[] storedKey = digest(keys.clientKey);
+
+ clientSignature = hmac(storedKey, authMessage);
+
+ } catch (final InvalidKeyException e) {
+ throw new AuthenticationException(e);
+ }
+
+ final byte[] clientProof = new byte[keys.clientKey.length];
+
+ for (int i = 0; i < clientProof.length; i++) {
+ clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
+ }
+
+
+ final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" +
+ Base64.encodeToString(clientProof, Base64.NO_WRAP);
+ state = State.RESPONSE_SENT;
+ return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
+ case RESPONSE_SENT:
+ try {
+ final String clientCalculatedServerFinalMessage = "v=" +
+ Base64.encodeToString(serverSignature, Base64.NO_WRAP);
+ if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
+ throw new Exception();
+ }
+ state = State.VALID_SERVER_RESPONSE;
+ return "";
+ } catch(Exception e) {
+ throw new AuthenticationException("Server final message does not match calculated final message");
+ }
+ default:
+ throw new InvalidStateException(state);
+ }
+ }
+
+ private static synchronized byte[] hmac(final byte[] key, final byte[] input)
+ throws InvalidKeyException {
+ HMAC.init(new KeyParameter(key));
+ HMAC.update(input, 0, input.length);
+ final byte[] out = new byte[HMAC.getMacSize()];
+ HMAC.doFinal(out, 0);
+ return out;
+ }
+
+ public static synchronized byte[] digest(byte[] bytes) {
+ DIGEST.reset();
+ DIGEST.update(bytes, 0, bytes.length);
+ final byte[] out = new byte[DIGEST.getDigestSize()];
+ DIGEST.doFinal(out, 0);
+ return out;
+ }
+
+ /*
+ * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the
+ * pseudorandom function (PRF) and with dkLen == output length of
+ * HMAC() == output length of H().
+ */
+ private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations)
+ throws InvalidKeyException {
+ byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE));
+ byte[] out = u.clone();
+ for (int i = 1; i < iterations; i++) {
+ u = hmac(key, u);
+ for (int j = 0; j < u.length; j++) {
+ out[j] ^= u[j];
+ }
+ }
+ return out;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java
index f40eec55..13593778 100644
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java
+++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java
@@ -1,77 +1,21 @@
package eu.siacs.conversations.crypto.sasl;
-import android.util.Base64;
-import android.util.LruCache;
-
-import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.macs.HMac;
-import org.bouncycastle.crypto.params.KeyParameter;
-import java.math.BigInteger;
-import java.nio.charset.Charset;
-import java.security.InvalidKeyException;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.TagWriter;
-public class ScramSha1 extends SaslMechanism {
- // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
- final private static String GS2_HEADER = "n,,";
- private String clientFirstMessageBare;
- final private String clientNonce;
- private byte[] serverSignature = null;
- private static HMac HMAC;
- private static Digest DIGEST;
- private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
- private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
-
- public static class KeyPair {
- final public byte[] clientKey;
- final public byte[] serverKey;
-
- public KeyPair(final byte[] clientKey, final byte[] serverKey) {
- this.clientKey = clientKey;
- this.serverKey = serverKey;
- }
- }
-
- private static final LruCache<String, KeyPair> CACHE;
-
+public class ScramSha1 extends ScramMechanism {
static {
DIGEST = new SHA1Digest();
HMAC = new HMac(new SHA1Digest());
- CACHE = new LruCache<String, KeyPair>(10) {
- protected KeyPair create(final String k) {
- // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
- // Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()'
- // is applied to prevent commas in the strings breaking things.
- final String[] kparts = k.split(",", 4);
- try {
- final byte[] saltedPassword, serverKey, clientKey;
- saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(),
- Base64.decode(CryptoHelper.hexToString(kparts[2]), Base64.DEFAULT), Integer.valueOf(kparts[3]));
- serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
- clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
-
- return new KeyPair(clientKey, serverKey);
- } catch (final InvalidKeyException | NumberFormatException e) {
- return null;
- }
- }
- };
}
- private State state = State.INITIAL;
-
public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
-
- // This nonce should be different for each authentication attempt.
- clientNonce = new BigInteger(100, this.rng).toString(32);
- clientFirstMessageBare = "";
}
@Override
@@ -83,156 +27,4 @@ public class ScramSha1 extends SaslMechanism {
public String getMechanism() {
return "SCRAM-SHA-1";
}
-
- @Override
- public String getClientFirstMessage() {
- if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
- clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) +
- ",r=" + this.clientNonce;
- state = State.AUTH_TEXT_SENT;
- }
- return Base64.encodeToString(
- (GS2_HEADER + clientFirstMessageBare).getBytes(Charset.defaultCharset()),
- Base64.NO_WRAP);
- }
-
- @Override
- public String getResponse(final String challenge) throws AuthenticationException {
- switch (state) {
- case AUTH_TEXT_SENT:
- if (challenge == null) {
- throw new AuthenticationException("challenge can not be null");
- }
- byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT);
- final Tokenizer tokenizer = new Tokenizer(serverFirstMessage);
- String nonce = "";
- int iterationCount = -1;
- String salt = "";
- for (final String token : tokenizer) {
- if (token.charAt(1) == '=') {
- switch (token.charAt(0)) {
- case 'i':
- try {
- iterationCount = Integer.parseInt(token.substring(2));
- } catch (final NumberFormatException e) {
- throw new AuthenticationException(e);
- }
- break;
- case 's':
- salt = token.substring(2);
- break;
- case 'r':
- nonce = token.substring(2);
- break;
- case 'm':
- /*
- * RFC 5802:
- * m: This attribute is reserved for future extensibility. In this
- * version of SCRAM, its presence in a client or a server message
- * MUST cause authentication failure when the attribute is parsed by
- * the other end.
- */
- throw new AuthenticationException("Server sent reserved token: `m'");
- }
- }
- }
-
- if (iterationCount < 0) {
- throw new AuthenticationException("Server did not send iteration count");
- }
- if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
- throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce);
- }
- if (salt.isEmpty()) {
- throw new AuthenticationException("Server sent empty salt");
- }
-
- final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString(
- GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce;
- final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
- + clientFinalMessageWithoutProof).getBytes();
-
- // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
- final KeyPair keys = CACHE.get(
- CryptoHelper.bytesToHex(account.getJid().toBareJid().toString().getBytes()) + ","
- + CryptoHelper.bytesToHex(account.getPassword().getBytes()) + ","
- + CryptoHelper.bytesToHex(salt.getBytes()) + ","
- + String.valueOf(iterationCount)
- );
- if (keys == null) {
- throw new AuthenticationException("Invalid keys generated");
- }
- final byte[] clientSignature;
- try {
- serverSignature = hmac(keys.serverKey, authMessage);
- final byte[] storedKey = digest(keys.clientKey);
-
- clientSignature = hmac(storedKey, authMessage);
-
- } catch (final InvalidKeyException e) {
- throw new AuthenticationException(e);
- }
-
- final byte[] clientProof = new byte[keys.clientKey.length];
-
- for (int i = 0; i < clientProof.length; i++) {
- clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
- }
-
-
- final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" +
- Base64.encodeToString(clientProof, Base64.NO_WRAP);
- state = State.RESPONSE_SENT;
- return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
- case RESPONSE_SENT:
- try {
- final String clientCalculatedServerFinalMessage = "v=" +
- Base64.encodeToString(serverSignature, Base64.NO_WRAP);
- if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
- throw new Exception();
- };
- state = State.VALID_SERVER_RESPONSE;
- return "";
- } catch(Exception e) {
- throw new AuthenticationException("Server final message does not match calculated final message");
- }
- default:
- throw new InvalidStateException(state);
- }
- }
-
- public static synchronized byte[] hmac(final byte[] key, final byte[] input)
- throws InvalidKeyException {
- HMAC.init(new KeyParameter(key));
- HMAC.update(input, 0, input.length);
- final byte[] out = new byte[HMAC.getMacSize()];
- HMAC.doFinal(out, 0);
- return out;
- }
-
- public static synchronized byte[] digest(byte[] bytes) {
- DIGEST.reset();
- DIGEST.update(bytes, 0, bytes.length);
- final byte[] out = new byte[DIGEST.getDigestSize()];
- DIGEST.doFinal(out, 0);
- return out;
- }
-
- /*
- * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the
- * pseudorandom function (PRF) and with dkLen == output length of
- * HMAC() == output length of H().
- */
- private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations)
- throws InvalidKeyException {
- byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE));
- byte[] out = u.clone();
- for (int i = 1; i < iterations; i++) {
- u = hmac(key, u);
- for (int j = 0; j < u.length; j++) {
- out[j] ^= u[j];
- }
- }
- return out;
- }
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java
new file mode 100644
index 00000000..1b7a969d
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java
@@ -0,0 +1,30 @@
+package eu.siacs.conversations.crypto.sasl;
+
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.macs.HMac;
+
+import java.security.SecureRandom;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.xml.TagWriter;
+
+public class ScramSha256 extends ScramMechanism {
+ static {
+ DIGEST = new SHA256Digest();
+ HMAC = new HMac(new SHA256Digest());
+ }
+
+ public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+ super(tagWriter, account, rng);
+ }
+
+ @Override
+ public int getPriority() {
+ return 25;
+ }
+
+ @Override
+ public String getMechanism() {
+ return "SCRAM-SHA-256";
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index ced48913..fa74c418 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -680,25 +680,41 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return this.nextCounterpart;
}
- private int getMostRecentlyUsedIncomingEncryption() {
- synchronized (this.messages) {
- for(int i = this.messages.size() -1; i >= 0; --i) {
- final Message m = this.messages.get(i);
- if (m.getStatus() == Message.STATUS_RECEIVED) {
- final int e = m.getEncryption();
- if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) {
- return Message.ENCRYPTION_PGP;
- } else {
- return e;
- }
+ public int getNextEncryption() {
+ return fixAvailableEncryption(this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, getDefaultEncryption()));
}
+
+ private int fixAvailableEncryption(int selectedEncryption) {
+ switch(selectedEncryption) {
+ case Message.ENCRYPTION_NONE:
+ return Config.supportUnencrypted() ? selectedEncryption : getDefaultEncryption();
+ case Message.ENCRYPTION_AXOLOTL:
+ return Config.supportOmemo() ? selectedEncryption : getDefaultEncryption();
+ case Message.ENCRYPTION_OTR:
+ return Config.supportOtr() ? selectedEncryption : getDefaultEncryption();
+ case Message.ENCRYPTION_PGP:
+ case Message.ENCRYPTION_DECRYPTED:
+ case Message.ENCRYPTION_DECRYPTION_FAILED:
+ return Config.supportOpenPgp() ? Message.ENCRYPTION_PGP : getDefaultEncryption();
+ default:
+ return getDefaultEncryption();
}
}
+
+ private int getDefaultEncryption() {
+ AxolotlService axolotlService = account.getAxolotlService();
+ if (Config.supportUnencrypted()) {
+ return Message.ENCRYPTION_NONE;
+ } else if (Config.supportOmemo()
+ && (axolotlService != null && axolotlService.isConversationAxolotlCapable(this) || !Config.multipleEncryptionChoices())) {
+ return Message.ENCRYPTION_AXOLOTL;
+ } else if (Config.supportOtr() && mode == MODE_SINGLE) {
+ return Message.ENCRYPTION_OTR;
+ } else if (Config.supportOpenPgp()) {
+ return Message.ENCRYPTION_PGP;
+ } else {
return Message.ENCRYPTION_NONE;
}
-
- public int getNextEncryption() {
- return Math.max(this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, Message.ENCRYPTION_NONE), Message.ENCRYPTION_NONE);
}
public void setNextEncryption(int encryption) {
diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
index 356856dc..970ace6f 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
@@ -120,7 +120,7 @@ public class HttpDownloadConnection implements Transferable {
} else {
message.setTransferable(null);
}
- mXmppConnectionService.updateConversationUi();
+ mHttpConnectionManager.updateConversationUi(true);
}
private void finish() {
@@ -131,7 +131,7 @@ public class HttpDownloadConnection implements Transferable {
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
notify = message.getConversation().getAccount().getPgpDecryptionService().decrypt(message, notify);
}
- mXmppConnectionService.updateConversationUi();
+ mHttpConnectionManager.updateConversationUi(true);
if (notify) {
mXmppConnectionService.getNotificationService().push(message);
}
@@ -139,7 +139,7 @@ public class HttpDownloadConnection implements Transferable {
private void changeStatus(int status) {
this.mStatus = status;
- mXmppConnectionService.updateConversationUi();
+ mHttpConnectionManager.updateConversationUi(true);
}
private void showToastForException(Exception e) {
@@ -340,7 +340,7 @@ public class HttpDownloadConnection implements Transferable {
public void updateProgress(int i) {
this.mProgress = i;
- mXmppConnectionService.updateConversationUi();
+ mHttpConnectionManager.updateConversationUi(false);
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
index 63a3884b..06a325d0 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
@@ -182,7 +182,7 @@ public class HttpUploadConnection implements Transferable {
while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) {
transmitted += count;
os.write(buffer, 0, count);
- mXmppConnectionService.updateConversationUi();
+ mHttpConnectionManager.updateConversationUi(false);
}
os.flush();
os.close();
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 3239ce3d..bc9ed259 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -555,6 +555,7 @@ public class FileBackend {
File file = new File(getAvatarPath(hash));
FileInputStream is = null;
try {
+ avatar.size = file.length();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
@@ -574,6 +575,7 @@ public class FileBackend {
avatar.image = new String(mByteArrayOutputStream.toByteArray());
avatar.height = options.outHeight;
avatar.width = options.outWidth;
+ avatar.type = options.outMimeType;
return avatar;
} catch (IOException e) {
return null;
@@ -593,6 +595,7 @@ public class FileBackend {
File file;
if (isAvatarCached(avatar)) {
file = new File(getAvatarPath(avatar.getFilename()));
+ avatar.size = file.length();
} else {
String filename = getAvatarPath(avatar.getFilename());
file = new File(filename + ".tmp");
@@ -604,7 +607,8 @@ public class FileBackend {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
- mDigestOutputStream.write(avatar.getImageAsBytes());
+ final byte[] bytes = avatar.getImageAsBytes();
+ mDigestOutputStream.write(bytes);
mDigestOutputStream.flush();
mDigestOutputStream.close();
String sha1sum = CryptoHelper.bytesToHex(digest.digest());
@@ -615,13 +619,13 @@ public class FileBackend {
file.delete();
return false;
}
+ avatar.size = bytes.length;
} catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) {
return false;
} finally {
close(os);
}
}
- avatar.size = file.length();
return true;
}
@@ -691,7 +695,7 @@ public class FileBackend {
Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(dest);
canvas.drawBitmap(source, null, targetRect, null);
- if (source != null && !source.isRecycled()) {
+ if (source.isRecycled()) {
source.recycle();
}
return dest;
diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
index dfe4cb28..18512997 100644
--- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.PowerManager;
+import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
@@ -22,6 +23,7 @@ import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.atomic.AtomicLong;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
@@ -36,6 +38,9 @@ import eu.siacs.conversations.entities.DownloadableFile;
public class AbstractConnectionManager {
protected XmppConnectionService mXmppConnectionService;
+ private static final int UI_REFRESH_THRESHOLD = 250;
+ private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0);
+
public AbstractConnectionManager(XmppConnectionService service) {
this.mXmppConnectionService = service;
}
@@ -136,6 +141,15 @@ public class AbstractConnectionManager {
}
}
+ public void updateConversationUi(boolean force) {
+ synchronized (LAST_UI_UPDATE_CALL) {
+ if (force || SystemClock.elapsedRealtime() - LAST_UI_UPDATE_CALL.get() >= UI_REFRESH_THRESHOLD) {
+ LAST_UI_UPDATE_CALL.set(SystemClock.elapsedRealtime());
+ mXmppConnectionService.updateConversationUi();
+ }
+ }
+ }
+
public PowerManager.WakeLock createWakeLock(String name) {
PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE);
return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,name);
diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
index a27e6c3e..a352ea8a 100644
--- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
+++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
@@ -111,7 +111,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
return null;
}
final Query query = new Query(conversation, start, end,PagingOrder.REVERSE);
+ if (start==0) {
query.reference = conversation.getFirstMamReference();
+ }
this.queries.add(query);
this.execute(query);
return query;
@@ -224,7 +226,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
this.finalizeQuery(query, done);
Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done));
if (query.getWith() == null && query.getMessageCount() > 0) {
- mXmppConnectionService.getNotificationService().finishBacklog(true);
+ mXmppConnectionService.getNotificationService().finishBacklog(true,query.getAccount());
}
} else {
final Query nextQuery;
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 47364b30..904392ee 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -100,12 +100,27 @@ public class NotificationService {
}
}
- public void finishBacklog(boolean notify) {
+ public void finishBacklog(boolean notify, Account account) {
synchronized (notifications) {
mXmppConnectionService.updateUnreadCountBadge();
+ if (account == null || !notify) {
updateNotification(notify);
+ } else {
+ boolean hasPendingMessages = false;
+ for(ArrayList<Message> messages : notifications.values()) {
+ if (messages.size() > 0 && messages.get(0).getConversation().getAccount() == account) {
+ hasPendingMessages = true;
+ break;
+ }
+ }
+ updateNotification(hasPendingMessages);
}
}
+ }
+
+ public void finishBacklog(boolean notify) {
+ finishBacklog(notify,null);
+ }
private void pushToStack(final Message message) {
final String conversationUuid = message.getConversationUuid();
@@ -507,7 +522,7 @@ public class NotificationService {
return (m.find() || message.getType() == Message.TYPE_PRIVATE);
}
- private static Pattern generateNickHighlightPattern(final String nick) {
+ public static Pattern generateNickHighlightPattern(final String nick) {
// We expect a word boundary, i.e. space or start of string, followed by
// the
// nick (matched in case-insensitive manner), followed by optional
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index ce49b2b6..e53b2524 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -2639,14 +2639,13 @@ public class XmppConnectionService extends Service {
}
public void publishAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- final IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
+ IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
this.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket result) {
if (result.getType() == IqPacket.TYPE.RESULT) {
- final IqPacket packet = XmppConnectionService.this.mIqGenerator
- .publishAvatarMetadata(avatar);
+ final IqPacket packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar);
sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket result) {
@@ -2655,25 +2654,22 @@ public class XmppConnectionService extends Service {
getAvatarService().clear(account);
databaseBackend.updateAccount(account);
}
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": published avatar "+(avatar.size/1024)+"KiB");
if (callback != null) {
callback.success(avatar);
- } else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": published avatar");
}
} else {
if (callback != null) {
- callback.error(
- R.string.error_publish_avatar_server_reject,
- avatar);
+ callback.error(R.string.error_publish_avatar_server_reject,avatar);
}
}
}
});
} else {
+ Element error = result.findChild("error");
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server rejected avatar "+(avatar.size/1024)+"KiB "+(error!=null?error.toString():""));
if (callback != null) {
- callback.error(
- R.string.error_publish_avatar_server_reject,
- avatar);
+ callback.error(R.string.error_publish_avatar_server_reject, avatar);
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index 69936b1d..1b76173a 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -116,6 +116,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
private Button mShowInactiveDevicesButton;
private QuickContactBadge badge;
private LinearLayout keys;
+ private LinearLayout keysWrapper;
private FlowLayout tags;
private boolean showDynamicTags = false;
private boolean showLastSeen = false;
@@ -220,6 +221,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
}
});
keys = (LinearLayout) findViewById(R.id.details_contact_keys);
+ keysWrapper = (LinearLayout) findViewById(R.id.keys_wrapper);
tags = (FlowLayout) findViewById(R.id.tags);
mShowInactiveDevicesButton = (Button) findViewById(R.id.show_inactive_devices);
if (getActionBar() != null) {
@@ -521,11 +523,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
});
keys.addView(view);
}
- if (hasKeys) {
- keys.setVisibility(View.VISIBLE);
- } else {
- keys.setVisibility(View.GONE);
- }
+ keysWrapper.setVisibility(hasKeys ? View.VISIBLE : View.GONE);
List<ListItem.Tag> tagList = contact.getTags(this);
if (tagList.size() == 0 || !this.showDynamicTags) {
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 11fa76c2..189879a6 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -99,6 +99,7 @@ public class ConversationActivity extends XmppActivity
private String mOpenConversation = null;
private boolean mPanelOpen = true;
+ private AtomicBoolean mShouldPanelBeOpen = new AtomicBoolean(false);
private Pair<Integer,Integer> mScrollPosition = null;
final private List<Uri> mPendingImageUris = new ArrayList<>();
final private List<Uri> mPendingFileUris = new ArrayList<>();
@@ -134,6 +135,7 @@ public class ConversationActivity extends XmppActivity
public void showConversationsOverview() {
if (mContentView instanceof SlidingPaneLayout) {
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ mShouldPanelBeOpen.set(true);
mSlidingPaneLayout.openPane();
}
}
@@ -151,6 +153,7 @@ public class ConversationActivity extends XmppActivity
public void hideConversationsOverview() {
if (mContentView instanceof SlidingPaneLayout) {
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ mShouldPanelBeOpen.set(false);
mSlidingPaneLayout.closePane();
}
}
@@ -161,8 +164,7 @@ public class ConversationActivity extends XmppActivity
public boolean isConversationsOverviewVisable() {
if (mContentView instanceof SlidingPaneLayout) {
- SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
- return mSlidingPaneLayout.isOpen();
+ return mShouldPanelBeOpen.get();
} else {
return true;
}
@@ -302,18 +304,19 @@ public class ConversationActivity extends XmppActivity
@Override
public void onPanelOpened(View arg0) {
+ mShouldPanelBeOpen.set(true);
updateActionBarTitle();
invalidateOptionsMenu();
hideKeyboard();
if (xmppConnectionServiceBound) {
- xmppConnectionService.getNotificationService()
- .setOpenConversation(null);
+ xmppConnectionService.getNotificationService().setOpenConversation(null);
}
closeContextMenu();
}
@Override
public void onPanelClosed(View arg0) {
+ mShouldPanelBeOpen.set(false);
listView.discardUndo();
openConversation();
}
@@ -1266,6 +1269,9 @@ public class ConversationActivity extends XmppActivity
if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService)) {
openBatteryOptimizationDialogIfNeeded();
}
+ if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) {
+ xmppConnectionService.getNotificationService().setOpenConversation(null);
+ }
}
private void handleViewConversationIntent(final Intent intent) {
@@ -1498,7 +1504,7 @@ public class ConversationActivity extends XmppActivity
private boolean hasAccountWithoutPush() {
for(Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED
- && !xmppConnectionService.getPushManagementService().available(account)) {
+ && !xmppConnectionService.getPushManagementService().availableAndUseful(account)) {
return true;
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 709244dd..e6d99579 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -34,6 +34,7 @@ import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.RelativeLayout;
@@ -849,16 +850,22 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
protected void highlightInConference(String nick) {
- String oldString = mEditMessage.getText().toString().trim();
- if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) {
+ final Editable editable = mEditMessage.getText();
+ String oldString = editable.toString().trim();
+ final int pos = mEditMessage.getSelectionStart();
+ if (oldString.isEmpty() || pos == 0) {
mEditMessage.getText().insert(0, nick + ": ");
} else {
- if (mEditMessage.getText().charAt(
- mEditMessage.getSelectionStart() - 1) != ' ') {
- nick = " " + nick;
+ final char before = editable.charAt(pos - 1);
+ final char after = editable.length() > pos ? editable.charAt(pos) : '\0';
+ if (before == '\n') {
+ editable.insert(pos, nick + ": ");
+ } else {
+ editable.insert(pos,(Character.isWhitespace(before)? "" : " ") + nick + (Character.isWhitespace(after) ? "" : " "));
+ if (Character.isWhitespace(after)) {
+ mEditMessage.setSelection(mEditMessage.getSelectionStart()+1);
+ }
}
- mEditMessage.getText().insert(mEditMessage.getSelectionStart(),
- nick + " ");
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 81d26b39..2080ddc0 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -836,7 +836,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
Features features = this.mAccount.getXmppConnection().getFeatures();
this.mStats.setVisibility(View.VISIBLE);
- boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
+ boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().availableAndUseful(mAccount) && isOptimizingBattery();
boolean showDataSaverWarning = isAffectedByDataSaver();
showOsOptimizationWarning(showBatteryWarning,showDataSaverWarning);
this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
diff --git a/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java b/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java
index 8ce8c14d..7929e073 100644
--- a/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java
@@ -24,8 +24,6 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.ui.TrustKeysActivity;
-import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.widget.Switch;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.XmppUri;
@@ -51,16 +49,17 @@ public abstract class OmemoActivity extends XmppActivity {
&& fingerprint instanceof String
&& fingerprintStatus instanceof FingerprintStatus) {
getMenuInflater().inflate(R.menu.omemo_key_context, menu);
- MenuItem purgeItem = menu.findItem(R.id.purge_omemo_key);
+ MenuItem distrust = menu.findItem(R.id.distrust_key);
MenuItem verifyScan = menu.findItem(R.id.verify_scan);
if (this instanceof TrustKeysActivity) {
- purgeItem.setVisible(false);
+ distrust.setVisible(false);
verifyScan.setVisible(false);
} else {
FingerprintStatus status = (FingerprintStatus) fingerprintStatus;
if (!status.isActive() || status.isVerified()) {
verifyScan.setVisible(false);
}
+ distrust.setVisible(status.isVerified());
}
this.mSelectedAccount = (Account) account;
this.mSelectedFingerprint = (String) fingerprint;
@@ -70,7 +69,7 @@ public abstract class OmemoActivity extends XmppActivity {
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.purge_omemo_key:
+ case R.id.distrust_key:
showPurgeKeyDialog(mSelectedAccount,mSelectedFingerprint);
break;
case R.id.copy_omemo_key:
@@ -242,17 +241,14 @@ public abstract class OmemoActivity extends XmppActivity {
public void showPurgeKeyDialog(final Account account, final String fingerprint) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(getString(R.string.purge_key));
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage(getString(R.string.purge_key_desc_part1)
- + "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
- + "\n\n" + getString(R.string.purge_key_desc_part2));
+ builder.setTitle(R.string.distrust_omemo_key);
+ builder.setMessage(R.string.distrust_omemo_key_text);
builder.setNegativeButton(getString(R.string.cancel), null);
- builder.setPositiveButton(getString(R.string.purge_key),
+ builder.setPositiveButton(R.string.confirm,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- account.getAxolotlService().purgeKey(fingerprint);
+ account.getAxolotlService().distrustFingerprint(fingerprint);
refreshUi();
}
});
diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
index 9a7414ef..f5724fc6 100644
--- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
@@ -310,15 +310,61 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
} else {
if (mReturnToPrevious && this.share.text != null && !this.share.text.isEmpty() ) {
final OnPresenceSelected callback = new OnPresenceSelected() {
+
+ private void finishAndSend(Message message) {
+ xmppConnectionService.sendMessage(message);
+ replaceToast(getString(R.string.shared_text_with_x, conversation.getName()));
+ finish();
+ }
+
+ private UiCallback<Message> messageEncryptionCallback = new UiCallback<Message>() {
+ @Override
+ public void success(final Message message) {
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ finishAndSend(message);
+ }
+ });
+ }
+
+ @Override
+ public void error(final int errorCode, Message object) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ replaceToast(getString(errorCode));
+ finish();
+ }
+ });
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+ finish();
+ }
+ };
+
@Override
public void onPresenceSelected() {
- Message message = new Message(conversation,share.text, conversation.getNextEncryption());
- if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
+
+ final int encryption = conversation.getNextEncryption();
+
+ Message message = new Message(conversation,share.text, encryption);
+
+ Log.d(Config.LOGTAG,"on presence selected encrpytion="+encryption);
+
+ if (encryption == Message.ENCRYPTION_PGP) {
+ replaceToast(getString(R.string.encrypting_message));
+ xmppConnectionService.getPgpEngine().encrypt(message,messageEncryptionCallback);
+ return;
+ }
+
+ if (encryption == Message.ENCRYPTION_OTR) {
message.setCounterpart(conversation.getNextCounterpart());
}
- xmppConnectionService.sendMessage(message);
- replaceToast(getString(R.string.shared_text_with_x, conversation.getName()));
- finish();
+ finishAndSend(message);
}
};
if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
index ce56e30c..a01689ec 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -52,6 +52,7 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Message.FileParams;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.NotificationService;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.text.DividerSpan;
import eu.siacs.conversations.ui.text.QuoteSpan;
@@ -88,6 +89,12 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
}
};
+ private static final Linkify.MatchFilter WEBURL_MATCH_FILTER = new Linkify.MatchFilter() {
+ @Override
+ public boolean acceptMatch(CharSequence charSequence, int start, int end) {
+ return start < 1 || charSequence.charAt(start-1) != '@';
+ }
+ };
private ConversationActivity activity;
@@ -442,8 +449,15 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
privateMarkerIndex + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
- Linkify.addLinks(body, Patterns.AUTOLINK_WEB_URL, "http", null, WEBURL_TRANSFORM_FILTER);
+ if (message.getConversation().getMode() == Conversation.MODE_MULTI && message.getStatus() == Message.STATUS_RECEIVED) {
+ Pattern pattern = NotificationService.generateNickHighlightPattern(message.getConversation().getMucOptions().getActualNick());
+ Matcher matcher = pattern.matcher(body);
+ while(matcher.find()) {
+ body.setSpan(new StyleSpan(Typeface.BOLD), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
Linkify.addLinks(body, XMPP_PATTERN, "xmpp");
+ Linkify.addLinks(body, Patterns.AUTOLINK_WEB_URL, "http", WEBURL_MATCH_FILTER, WEBURL_TRANSFORM_FILTER);
Linkify.addLinks(body, GeoHelper.GEO_URI, "geo");
viewHolder.messageBody.setAutoLinkMask(0);
viewHolder.messageBody.setText(body);
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 7cd4707d..08dbdc18 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -13,8 +13,6 @@ import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
-import org.json.JSONException;
-import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
@@ -26,8 +24,8 @@ import java.net.IDN;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.net.UnknownHostException;
import java.net.URL;
+import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
@@ -61,6 +59,7 @@ import eu.siacs.conversations.crypto.sasl.External;
import eu.siacs.conversations.crypto.sasl.Plain;
import eu.siacs.conversations.crypto.sasl.SaslMechanism;
import eu.siacs.conversations.crypto.sasl.ScramSha1;
+import eu.siacs.conversations.crypto.sasl.ScramSha256;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
@@ -178,8 +177,6 @@ public class XmppConnection implements Runnable {
}
}
- private Identity mServerIdentity = Identity.UNKNOWN;
-
public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
@@ -253,17 +250,6 @@ public class XmppConnection implements Runnable {
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting");
features.encryptionEnabled = false;
this.attempt++;
- switch (account.getJid().getDomainpart()) {
- case "chat.facebook.com":
- mServerIdentity = Identity.FACEBOOK;
- break;
- case "nimbuzz.com":
- mServerIdentity = Identity.NIMBUZZ;
- break;
- default:
- mServerIdentity = Identity.UNKNOWN;
- break;
- }
try {
Socket localSocket;
shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER);
@@ -747,7 +733,7 @@ public class XmppConnection implements Runnable {
final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId());
// Packets to the server should have responses from the server
if (packetCallbackDuple.first.toServer(account)) {
- if (packet.fromServer(account) || mServerIdentity == Identity.FACEBOOK) {
+ if (packet.fromServer(account)) {
callback = packetCallbackDuple.second;
packetCallbacks.remove(packet.getId());
} else {
@@ -868,6 +854,8 @@ public class XmppConnection implements Runnable {
auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) {
saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG());
+ } else if (mechanisms.contains("SCRAM-SHA-256")) {
+ saslMechanism = new ScramSha256(tagWriter, account, mXmppConnectionService.getRNG());
} else if (mechanisms.contains("SCRAM-SHA-1")) {
saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG());
} else if (mechanisms.contains("PLAIN")) {
@@ -1099,7 +1087,7 @@ public class XmppConnection implements Runnable {
this.disco.clear();
}
mPendingServiceDiscoveries.set(0);
- mWaitForDisco.set(mServerIdentity != Identity.NIMBUZZ && smVersion != 0);
+ mWaitForDisco.set(smVersion != 0 && !account.getJid().getDomainpart().equalsIgnoreCase("nimbuzz.com"));
lastDiscoStarted = SystemClock.elapsedRealtime();
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery");
mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
@@ -1138,24 +1126,6 @@ public class XmppConnection implements Runnable {
boolean advancedStreamFeaturesLoaded;
synchronized (XmppConnection.this.disco) {
ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet);
- for (final ServiceDiscoveryResult.Identity id : result.getIdentities()) {
- if (mServerIdentity == Identity.UNKNOWN && id.getType().equals("im") &&
- id.getCategory().equals("server") && id.getName() != null &&
- jid.equals(account.getServer())) {
- switch (id.getName()) {
- case "Prosody":
- mServerIdentity = Identity.PROSODY;
- break;
- case "ejabberd":
- mServerIdentity = Identity.EJABBERD;
- break;
- case "Slack-XMPP":
- mServerIdentity = Identity.SLACK;
- break;
- }
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server name: " + id.getName());
- }
- }
if (jid.equals(account.getServer())) {
mXmppConnectionService.databaseBackend.insertDiscoveryResult(result);
}
@@ -1540,7 +1510,25 @@ public class XmppConnection implements Runnable {
}
public Identity getServerIdentity() {
- return mServerIdentity;
+ synchronized (this.disco) {
+ ServiceDiscoveryResult result = disco.get(account.getJid().toDomainJid());
+ if (result == null) {
+ return Identity.UNKNOWN;
+ }
+ for (final ServiceDiscoveryResult.Identity id : result.getIdentities()) {
+ if (id.getType().equals("im") && id.getCategory().equals("server") && id.getName() != null) {
+ switch (id.getName()) {
+ case "Prosody":
+ return Identity.PROSODY;
+ case "ejabberd":
+ return Identity.EJABBERD;
+ case "Slack-XMPP":
+ return Identity.SLACK;
+ }
+ }
+ }
+ }
+ return Identity.UNKNOWN;
}
private class UnauthorizedException extends IOException {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
index 5461b9c6..0c0c054d 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -388,7 +388,7 @@ public class JingleConnection implements Transferable {
long size = Long.parseLong(fileSize.getContent());
message.setBody(Long.toString(size));
conversation.add(message);
- mXmppConnectionService.updateConversationUi();
+ mJingleConnectionManager.updateConversationUi(true);
if (mJingleConnectionManager.hasStoragePermission()
&& size < this.mJingleConnectionManager.getAutoAcceptFileSize()
&& mXmppConnectionService.isDataSaverDisabled()) {
@@ -510,7 +510,7 @@ public class JingleConnection implements Transferable {
private void sendAccept() {
mJingleStatus = JINGLE_STATUS_ACCEPTED;
this.mStatus = Transferable.STATUS_DOWNLOADING;
- mXmppConnectionService.updateConversationUi();
+ this.mJingleConnectionManager.updateConversationUi(true);
this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
@Override
public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) {
@@ -842,7 +842,7 @@ public class JingleConnection implements Transferable {
if (this.file!=null) {
file.delete();
}
- this.mXmppConnectionService.updateConversationUi();
+ this.mJingleConnectionManager.updateConversationUi(true);
} else {
this.mXmppConnectionService.markMessage(this.message,
Message.STATUS_SEND_FAILED);
@@ -868,7 +868,7 @@ public class JingleConnection implements Transferable {
if (this.file!=null) {
file.delete();
}
- this.mXmppConnectionService.updateConversationUi();
+ this.mJingleConnectionManager.updateConversationUi(true);
} else {
this.mXmppConnectionService.markMessage(this.message,
Message.STATUS_SEND_FAILED,
@@ -1016,7 +1016,7 @@ public class JingleConnection implements Transferable {
public void updateProgress(int i) {
this.mProgress = i;
- mXmppConnectionService.updateConversationUi();
+ mJingleConnectionManager.updateConversationUi(false);
}
public String getTransportId() {