diff --git a/build.gradle b/build.gradle
index 3a0441356..06aa31ae5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -58,6 +58,10 @@ dependencies {
implementation "androidx.security:security-crypto:1.0.0"
+
+ implementation 'org.slf4j:slf4j-api:1.7.36'
+ implementation 'com.github.tony19:logback-android:2.0.1'
+
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.9'
diff --git a/proguard-rules.pro b/proguard-rules.pro
index 7e4d7d31d..b9bab8865 100644
--- a/proguard-rules.pro
+++ b/proguard-rules.pro
@@ -13,6 +13,10 @@
-keep class org.openintents.openpgp.*
-keep class org.webrtc.** { *; }
+# Logger
+-keep class org.slf4j.** {*;}
+-keep class ch.qos.** {*;}
+
-dontwarn javax.mail.internet.MimeMessage
-dontwarn javax.mail.internet.MimeBodyPart
-dontwarn javax.mail.internet.SharedInputStream
diff --git a/src/main/assets/logback.xml b/src/main/assets/logback.xml
new file mode 100644
index 000000000..0c5ac873f
--- /dev/null
+++ b/src/main/assets/logback.xml
@@ -0,0 +1,16 @@
+
+
+
+ conversations
+
+
+ %logger{12}: %msg
+
+
+
+
+
+
+
diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java
index 2791ff827..66566e12e 100644
--- a/src/main/java/eu/siacs/conversations/xml/Namespace.java
+++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java
@@ -79,4 +79,5 @@ public final class Namespace {
"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push";
public static final String JABBER_CLIENT = "jabber:client";
+ public static final String FORWARD = "urn:xmpp:forward:0";
}
diff --git a/src/main/java/im/conversations/android/database/dao/PresenceDao.java b/src/main/java/im/conversations/android/database/dao/PresenceDao.java
index 1ba5a7836..9e406792c 100644
--- a/src/main/java/im/conversations/android/database/dao/PresenceDao.java
+++ b/src/main/java/im/conversations/android/database/dao/PresenceDao.java
@@ -3,8 +3,9 @@ package im.conversations.android.database.dao;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
import androidx.room.Query;
-import androidx.room.Upsert;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.database.entity.PresenceEntity;
import im.conversations.android.database.model.Account;
@@ -26,7 +27,7 @@ public abstract class PresenceDao {
+ " resource=:resource")
abstract void deletePresence(long account, Jid address, String resource);
- @Upsert
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract void insert(PresenceEntity entity);
public void set(
diff --git a/src/main/java/im/conversations/android/database/model/PresenceType.java b/src/main/java/im/conversations/android/database/model/PresenceType.java
index 943c96f55..7407f2314 100644
--- a/src/main/java/im/conversations/android/database/model/PresenceType.java
+++ b/src/main/java/im/conversations/android/database/model/PresenceType.java
@@ -12,6 +12,6 @@ public enum PresenceType {
if (typeAttribute == null) {
return null;
}
- return of(typeAttribute.toUpperCase(Locale.ROOT));
+ return valueOf(typeAttribute.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/im/conversations/android/xml/TagWriter.java b/src/main/java/im/conversations/android/xml/TagWriter.java
index 221805eb7..6e660ed23 100644
--- a/src/main/java/im/conversations/android/xml/TagWriter.java
+++ b/src/main/java/im/conversations/android/xml/TagWriter.java
@@ -1,7 +1,5 @@
package im.conversations.android.xml;
-import android.util.Log;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Tag;
import im.conversations.android.xmpp.model.StreamElement;
@@ -11,9 +9,13 @@ import java.io.OutputStreamWriter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class TagWriter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(TagWriter.class);
+
private OutputStreamWriter outputStream;
private boolean finished = false;
private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue<>();
@@ -83,7 +85,7 @@ public class TagWriter {
public void writeStanzaAsync(StreamElement stanza) {
if (finished) {
- Log.d(Config.LOGTAG, "attempting to write stanza to finished TagWriter");
+ LOGGER.info("attempting to write stanza to finished TagWriter");
} else {
if (!asyncStanzaWriter.isAlive()) {
try {
diff --git a/src/main/java/im/conversations/android/xmpp/ConnectionPool.java b/src/main/java/im/conversations/android/xmpp/ConnectionPool.java
index cf218e6ab..b7cbbe385 100644
--- a/src/main/java/im/conversations/android/xmpp/ConnectionPool.java
+++ b/src/main/java/im/conversations/android/xmpp/ConnectionPool.java
@@ -4,7 +4,6 @@ import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.content.Context;
import android.os.SystemClock;
-import android.util.Log;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -28,9 +27,13 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class ConnectionPool {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPool.class);
+
private static volatile ConnectionPool INSTANCE;
private final Context context;
@@ -137,7 +140,7 @@ public class ConnectionPool {
if (connection.getStatus() == ConnectionState.ONLINE) {
synchronized (lowPingTimeoutMode) {
if (lowPingTimeoutMode.remove(account.address)) {
- Log.d(Config.LOGTAG, account.address + ": leaving low ping timeout mode");
+ LOGGER.debug("{}: leaving low ping timeout mode", account.address);
}
}
ConversationsDatabase.getInstance(context)
@@ -154,11 +157,9 @@ public class ConnectionPool {
// resetSendingToWaiting(account);
if (isInLowPingTimeoutMode(account)) {
- Log.d(
- Config.LOGTAG,
- account.address
- + ": went into offline state during low ping mode."
- + " reconnecting now");
+ LOGGER.debug(
+ "{}: went into offline state during low ping mode. reconnecting now",
+ account.address);
reconnectAccount(connection);
} else {
final int timeToReconnect = SECURE_RANDOM.nextInt(10) + 2;
@@ -173,24 +174,20 @@ public class ConnectionPool {
final int next = connection.getTimeToNextAttempt();
final boolean lowPingTimeoutMode = isInLowPingTimeoutMode(account);
if (next <= 0) {
- Log.d(
- Config.LOGTAG,
- account.address
- + ": error connecting account. reconnecting now."
- + " lowPingTimeout="
- + lowPingTimeoutMode);
+ LOGGER.debug(
+ "{}: error connecting account. reconnecting now. lowPingTimeout={}",
+ account.address,
+ lowPingTimeoutMode);
reconnectAccount(connection);
} else {
final int attempt = connection.getAttempt() + 1;
- Log.d(
- Config.LOGTAG,
- account.address
- + ": error connecting account. try again in "
- + next
- + "s for the "
- + attempt
- + " time. lowPingTimeout="
- + lowPingTimeoutMode);
+ LOGGER.debug(
+ "{}: error connecting account. try again in {}s for the {} time."
+ + " lowPingTimeout={}",
+ account.address,
+ next,
+ attempt,
+ lowPingTimeoutMode);
scheduleWakeUpCall(next);
}
}
@@ -246,9 +243,7 @@ public class ConnectionPool {
final Account account = xmppConnection.getAccount();
final boolean lowTimeout = isInLowPingTimeoutMode(account);
xmppConnection.sendPing();
- Log.d(
- Config.LOGTAG,
- account.address + " send ping (lowTimeout=" + lowTimeout + ")");
+ LOGGER.debug("{}: send ping (lowTimeout={})", account.address, lowTimeout);
scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT);
}
}
@@ -277,7 +272,7 @@ public class ConnectionPool {
(lastSent + pingTimeout) - SystemClock.elapsedRealtime();
if (lastSent > lastReceived) {
if (pingTimeoutIn < 0) {
- Log.d(Config.LOGTAG, account.address + ": ping timeout");
+ LOGGER.debug("{}: ping timeout", account.address);
this.reconnectAccount(connection);
} else {
this.scheduleWakeUpCall(Ints.saturatedCast(pingTimeoutIn / 1000));
@@ -287,18 +282,14 @@ public class ConnectionPool {
if (isAccountPushed) {
pingNow = true;
if (lowPingTimeoutMode.add(account.address)) {
- Log.d(
- Config.LOGTAG,
- account.address + ": entering low ping timeout mode");
+ LOGGER.debug("{}: entering low ping timeout mode", account.address);
}
} else if (msToNextPing <= 0) {
pingNow = true;
} else {
this.scheduleWakeUpCall(Ints.saturatedCast(msToNextPing / 1000));
if (lowPingTimeoutMode.remove(account.address)) {
- Log.d(
- Config.LOGTAG,
- account.address + ": leaving low ping timeout mode");
+ LOGGER.debug("{}: leaving low ping timeout mode", account.address);
}
}
}
@@ -310,13 +301,10 @@ public class ConnectionPool {
(SystemClock.elapsedRealtime() - connection.getLastConnect()) / 1000;
long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
if (timeout < 0) {
- Log.d(
- Config.LOGTAG,
- account.address
- + ": time out during connect reconnecting"
- + " (secondsSinceLast="
- + secondsSinceLastConnect
- + ")");
+ LOGGER.debug(
+ "{}: time out during connect reconnecting (secondsSinceLast={})",
+ account.address,
+ secondsSinceLastConnect);
connection.resetAttemptCount(false);
reconnectAccount(connection);
}
diff --git a/src/main/java/im/conversations/android/xmpp/XmppConnection.java b/src/main/java/im/conversations/android/xmpp/XmppConnection.java
index 7c884eb44..cc056ee2b 100644
--- a/src/main/java/im/conversations/android/xmpp/XmppConnection.java
+++ b/src/main/java/im/conversations/android/xmpp/XmppConnection.java
@@ -2259,9 +2259,15 @@ public class XmppConnection implements Runnable {
public boolean fromAccount(final Stanza stanza) {
final Jid from = stanza.getFrom();
+ // TODO null is valid too?!
return from != null && from.asBareJid().equals(connectionAddress.asBareJid());
}
+ public boolean toAccount(final Stanza stanza) {
+ final Jid to = stanza.getTo();
+ return to == null || to.asBareJid().equals(connectionAddress.asBareJid());
+ }
+
public boolean supportsClientStateIndication() {
return this.streamFeatures != null && this.streamFeatures.clientStateIndication();
}
diff --git a/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java b/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java
index 4fc354245..60c0a2349 100644
--- a/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java
+++ b/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java
@@ -1,18 +1,26 @@
package im.conversations.android.xmpp.manager;
import android.content.Context;
-import android.util.Log;
-import eu.siacs.conversations.Config;
import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.carbons.Enable;
+import im.conversations.android.xmpp.model.carbons.Received;
+import im.conversations.android.xmpp.model.carbons.Sent;
import im.conversations.android.xmpp.model.stanza.IQ;
+import im.conversations.android.xmpp.processor.MessageProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class CarbonsManager extends AbstractManager {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CarbonsManager.class);
+
+ private final MessageProcessor messageProcessor;
+
private boolean enabled = false;
public CarbonsManager(Context context, XmppConnection connection) {
super(context, connection);
+ this.messageProcessor = new MessageProcessor(context, connection, false);
}
public void enable() {
@@ -22,14 +30,11 @@ public class CarbonsManager extends AbstractManager {
iq,
result -> {
if (result.getType() == IQ.Type.RESULT) {
- Log.d(
- Config.LOGTAG,
- getAccount().address + ": successfully enabled carbons");
+ LOGGER.info("{}: successfully enabled carbons", getAccount().address);
this.enabled = true;
} else {
- Log.d(
- Config.LOGTAG,
- getAccount().address + ": could not enable carbons " + result);
+ LOGGER.warn(
+ "{}: could not enable carbons {}", getAccount().address, result);
}
});
}
@@ -41,4 +46,30 @@ public class CarbonsManager extends AbstractManager {
public boolean isEnabled() {
return this.enabled;
}
+
+ public void handleReceived(final Received received) {
+ final var forwarded = received.getForwarded();
+ final var message = forwarded == null ? null : forwarded.getMessage();
+ if (message == null) {
+ LOGGER.warn("Received carbon copy did not contain forwarded message");
+ } else if (connection.toAccount(message)) {
+ // all received, forwarded messages must be addressed to us
+ this.messageProcessor.accept(message);
+ } else {
+ LOGGER.warn("Received carbon copy had invalid `to` attribute {}", message.getTo());
+ }
+ }
+
+ public void handleSent(final Sent sent) {
+ final var forwarded = sent.getForwarded();
+ final var message = forwarded == null ? null : forwarded.getMessage();
+ if (message == null) {
+ LOGGER.warn("Sent carbon copy did not contain forwarded message");
+ } else if (connection.fromAccount(message)) {
+ // all sent, forwarded messages must be addressed from us
+ this.messageProcessor.accept(message);
+ } else {
+ LOGGER.warn("Sent carbon copy had invalid `from` attribute {}", message.getFrom());
+ }
+ }
}
diff --git a/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java b/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java
index ebe4f64a4..300c8e317 100644
--- a/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java
+++ b/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java
@@ -1,18 +1,20 @@
package im.conversations.android.xmpp.manager;
import android.content.Context;
-import android.util.Log;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
-import eu.siacs.conversations.Config;
import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.roster.Item;
import im.conversations.android.xmpp.model.roster.Query;
import im.conversations.android.xmpp.model.stanza.IQ;
import java.util.Objects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class RosterManager extends AbstractManager {
+ private static final Logger LOGGER = LoggerFactory.getLogger(RosterManager.class);
+
public RosterManager(final Context context, final XmppConnection connection) {
super(context, connection);
}
@@ -31,9 +33,9 @@ public class RosterManager extends AbstractManager {
final Query rosterQuery = new Query();
iqPacket.addChild(rosterQuery);
if (Strings.isNullOrEmpty(rosterVersion)) {
- Log.d(Config.LOGTAG, account.address + ": fetching roster");
+ LOGGER.info("{}: fetching roster", account.address);
} else {
- Log.d(Config.LOGTAG, account.address + ": fetching roster version " + rosterVersion);
+ LOGGER.info("{}: fetching roster version {}", account.address, rosterVersion);
rosterQuery.setVersion(rosterVersion);
}
connection.sendIqPacket(iqPacket, this::handleFetchResult);
@@ -52,7 +54,7 @@ public class RosterManager extends AbstractManager {
final var database = getDatabase();
final var version = query.getVersion();
final var items = query.getExtensions(Item.class);
- // In a roster result (Section 2.1.4), the client MUST ignore values of the c'subscription'
+ // In a roster result (Section 2.1.4), the client MUST ignore values of the 'subscription'
// attribute other than "none", "to", "from", or "both".
final var validItems =
Collections2.filter(
diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java
new file mode 100644
index 000000000..507869a60
--- /dev/null
+++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.carbons;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.forward.Forwarded;
+
+@XmlElement
+public class Received extends Extension {
+
+ public Received() {
+ super(Received.class);
+ }
+
+ public Forwarded getForwarded() {
+ return this.getExtension(Forwarded.class);
+ }
+}
diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java
new file mode 100644
index 000000000..0201c53c6
--- /dev/null
+++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.carbons;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.forward.Forwarded;
+
+@XmlElement
+public class Sent extends Extension {
+
+ public Sent() {
+ super(Sent.class);
+ }
+
+ public Forwarded getForwarded() {
+ return this.getExtension(Forwarded.class);
+ }
+}
diff --git a/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java b/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java
new file mode 100644
index 000000000..80a646a41
--- /dev/null
+++ b/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java
@@ -0,0 +1,18 @@
+package im.conversations.android.xmpp.model.forward;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.stanza.Message;
+
+@XmlElement(namespace = Namespace.FORWARD)
+public class Forwarded extends Extension {
+
+ public Forwarded() {
+ super(Forwarded.class);
+ }
+
+ public Message getMessage() {
+ return this.getExtension(Message.class);
+ }
+}
diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Item.java b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java
index 8edd96d19..4c867f81d 100644
--- a/src/main/java/im/conversations/android/xmpp/model/roster/Item.java
+++ b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java
@@ -32,7 +32,7 @@ public class Item extends Extension {
public Subscription getSubscription() {
final String value = this.getAttribute("subscription");
try {
- return value == null ? null : Subscription.valueOf(value.toLowerCase(Locale.ROOT));
+ return value == null ? null : Subscription.valueOf(value.toUpperCase(Locale.ROOT));
} catch (final IllegalArgumentException e) {
return null;
}
diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java
index 5340833ae..8ae877fb2 100644
--- a/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java
+++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java
@@ -8,4 +8,8 @@ public class Message extends Stanza {
public Message() {
super(Message.class);
}
+
+ public String getBody() {
+ return this.findChildContent("body");
+ }
}
diff --git a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java
index 80147fffd..a1728c09c 100644
--- a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java
+++ b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java
@@ -9,6 +9,7 @@ import im.conversations.android.xmpp.manager.BlockingManager;
import im.conversations.android.xmpp.manager.BookmarkManager;
import im.conversations.android.xmpp.manager.DiscoManager;
import im.conversations.android.xmpp.manager.RosterManager;
+import im.conversations.android.xmpp.model.stanza.Presence;
import java.util.function.Consumer;
public class BindProcessor extends XmppConnection.Delegate implements Consumer {
@@ -46,6 +47,8 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer {
+public class MessageProcessor extends XmppConnection.Delegate implements Consumer {
- public MessageProcessor(final Context context, final XmppConnection connection) {}
+ private static final Logger LOGGER = LoggerFactory.getLogger(MessageProcessor.class);
+
+ private final boolean isRoot;
+
+ public MessageProcessor(final Context context, final XmppConnection connection) {
+ this(context, connection, true);
+ }
+
+ public MessageProcessor(
+ final Context context, final XmppConnection connection, final boolean isRoot) {
+ super(context, connection);
+ this.isRoot = isRoot;
+ }
@Override
- public void accept(final Message messagePacket) {}
+ public void accept(final Message message) {
+
+ if (isRoot && connection.fromServer(message) && message.hasExtension(Received.class)) {
+ getManager(CarbonsManager.class).handleReceived(message.getExtension(Received.class));
+ }
+
+ if (isRoot && connection.fromServer(message) && message.hasExtension(Sent.class)) {
+ getManager(CarbonsManager.class).handleSent(message.getExtension(Sent.class));
+ }
+
+ final String body = message.getBody();
+ if (!Strings.isNullOrEmpty(body)) {
+ LOGGER.info("'{}' from {}", body, message.getFrom());
+ }
+ }
}