diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index f7c40269e..a3ebb9730 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -832,7 +832,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (!account.getJid().asBareJid().equals(from.asBareJid())) {
processMessageReceipts(account, packet, query);
}
- mXmppConnectionService.getJingleConnectionManager().deliverMessage(account, packet.getTo(), packet.getFrom(), child);
+ mXmppConnectionService.getJingleConnectionManager().deliverMessage(account, packet.getTo(), packet.getFrom(), child, serverMsgId, timestamp);
break;
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
index 908e572cd..30a62bedf 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -31,6 +31,7 @@ import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presence;
+import eu.siacs.conversations.entities.RtpSessionStatus;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.services.ExportBackupService;
import rocks.xmpp.addr.Jid;
@@ -300,7 +301,13 @@ public class UIHelper {
} else if (message.isFileOrImage()) {
return new Pair<>(getFileDescriptionString(context, message), true);
} else if (message.getType() == Message.TYPE_RTP_SESSION) {
- return new Pair<>(context.getString(message.getStatus() == Message.STATUS_RECEIVED ? R.string.incoming_call : R.string.outgoing_call), true);
+ RtpSessionStatus rtpSessionStatus = RtpSessionStatus.of(message.getBody());
+ final boolean received = message.getStatus() == Message.STATUS_RECEIVED;
+ if (!rtpSessionStatus.successful && received) {
+ return new Pair<>(context.getString(R.string.missed_call),true);
+ } else {
+ return new Pair<>(context.getString(received ? R.string.incoming_call : R.string.outgoing_call), true);
+ }
} else {
final String body = MessageUtils.filterLtrRtl(message.getBody());
if (body.startsWith(Message.ME_COMMAND)) {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
index 3a7c179e4..7df61767b 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
@@ -14,6 +14,8 @@ import java.util.concurrent.ConcurrentHashMap;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Conversational;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.services.AbstractConnectionManager;
@@ -108,7 +110,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
account.getXmppConnection().sendIqPacket(response, null);
}
- public void deliverMessage(final Account account, final Jid to, final Jid from, final Element message) {
+ public void deliverMessage(final Account account, final Jid to, final Jid from, final Element message, String serverMsgId, long timestamp) {
Preconditions.checkArgument(Namespace.JINGLE_MESSAGE.equals(message.getNamespace()));
final String sessionId = message.getAttribute("id");
if (sessionId == null) {
@@ -120,7 +122,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
final JingleRtpConnection rtpConnection = (JingleRtpConnection) connection;
final AbstractJingleConnection.Id id = connection.getId();
if (id.account == account && id.sessionId.equals(sessionId)) {
- rtpConnection.deliveryMessage(from, message);
+ rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
return;
}
}
@@ -141,7 +143,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
final AbstractJingleConnection existingJingleConnection = connections.get(id);
if (existingJingleConnection != null) {
if (existingJingleConnection instanceof JingleRtpConnection) {
- ((JingleRtpConnection) existingJingleConnection).deliveryMessage(from, message);
+ ((JingleRtpConnection) existingJingleConnection).deliveryMessage(from, message, serverMsgId, timestamp);
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + existingJingleConnection.getClass().getName() + " does not support jingle messages");
}
@@ -162,7 +164,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} else {
final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
this.connections.put(id, rtpConnection);
- rtpConnection.deliveryMessage(from, message);
+ rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
}
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to react to proposed " + namespace + " session");
@@ -175,7 +177,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, account.getJid());
this.connections.put(id, rtpConnection);
rtpConnection.transitionOrThrow(AbstractJingleConnection.State.PROPOSED);
- rtpConnection.deliveryMessage(from, message);
+ rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": no rtp session proposal found for " + from + " to deliver proceed");
}
@@ -184,6 +186,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
final RtpSessionProposal proposal = new RtpSessionProposal(account, from.asBareJid(), sessionId);
synchronized (rtpSessionProposals) {
if (rtpSessionProposals.remove(proposal) != null) {
+ writeLogMissedOutgoing(account, proposal.with, proposal.sessionId, serverMsgId, timestamp);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, proposal.with, proposal.sessionId, RtpEndUserState.DECLINED_OR_BUSY);
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": no rtp session proposal found for " + from + " to deliver reject");
@@ -195,6 +198,35 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
+ private void writeLogMissedOutgoing(final Account account, Jid with, final String sessionId, String serverMsgId, long timestamp) {
+ final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
+ account,
+ with.asBareJid(),
+ false,
+ false
+ );
+ final Message message = new Message(
+ conversation,
+ Message.STATUS_SEND,
+ Message.TYPE_RTP_SESSION,
+ sessionId
+ );
+ message.setServerMsgId(serverMsgId);
+ message.setTime(timestamp);
+ writeMessage(message);
+ }
+
+ private void writeMessage(final Message message) {
+ final Conversational conversational = message.getConversation();
+ if (conversational instanceof Conversation) {
+ ((Conversation) conversational).add(message);
+ mXmppConnectionService.databaseBackend.createMessage(message);
+ mXmppConnectionService.updateConversationUi();
+ } else {
+ throw new IllegalStateException("Somehow the conversation in a message was a stub");
+ }
+ }
+
public void startJingleFileTransfer(final Message message) {
Preconditions.checkArgument(message.isFileOrImage(), "Message is not of type file or image");
final Transferable old = message.getTransferable();
@@ -271,7 +303,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (matchingProposal != null) {
this.rtpSessionProposals.remove(matchingProposal);
final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(matchingProposal);
- Log.d(Config.LOGTAG, messagePacket.toString());
+ writeLogMissedOutgoing(account, matchingProposal.with, matchingProposal.sessionId, null, System.currentTimeMillis());
mXmppConnectionService.sendMessagePacket(account, messagePacket);
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
index a375ac657..de1edf97c 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
@@ -371,23 +371,23 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
send(sessionAccept);
}
- void deliveryMessage(final Jid from, final Element message) {
+ void deliveryMessage(final Jid from, final Element message, final String serverMessageId, final long timestamp) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": delivered message to JingleRtpConnection " + message);
switch (message.getName()) {
case "propose":
- receivePropose(from, message);
+ receivePropose(from, serverMessageId, timestamp);
break;
case "proceed":
- receiveProceed(from, message);
+ receiveProceed(from, serverMessageId, timestamp);
break;
case "retract":
- receiveRetract(from, message);
+ receiveRetract(from, serverMessageId, timestamp);
break;
case "reject":
- receiveReject(from, message);
+ receiveReject(from, serverMessageId, timestamp);
break;
case "accept":
- receiveAccept(from, message);
+ receiveAccept(from, serverMessageId, timestamp);
break;
default:
break;
@@ -403,10 +403,16 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
}
- private void receiveAccept(Jid from, Element message) {
+ private void receiveAccept(final Jid from, final String serverMsgId, final long timestamp) {
final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
if (originatedFromMyself) {
if (transition(State.ACCEPTED)) {
+ if (serverMsgId != null) {
+ this.message.setServerMsgId(serverMsgId);
+ }
+ this.message.setTime(timestamp);
+ this.message.setCarbon(true); //indicate that call was accepted on other device
+ this.writeLogMessageSuccess(0);
this.xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
this.jingleConnectionManager.finishConnection(this);
} else {
@@ -417,13 +423,19 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
}
- private void receiveReject(Jid from, Element message) {
+ private void receiveReject(Jid from, String serverMsgId, long timestamp) {
final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
//reject from another one of my clients
if (originatedFromMyself) {
if (transition(State.REJECTED)) {
this.xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
this.jingleConnectionManager.finishConnection(this);
+ if (serverMsgId != null) {
+ this.message.setServerMsgId(serverMsgId);
+ }
+ this.message.setTime(timestamp);
+ this.message.setCarbon(true); //indicate that call was rejected on other device
+ writeLogMessageMissed();
} else {
Log.d(Config.LOGTAG, "not able to transition into REJECTED because already in " + this.state);
}
@@ -432,12 +444,15 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
}
- private void receivePropose(final Jid from, final Element propose) {
+ private void receivePropose(final Jid from, final String serverMsgId, final long timestamp) {
final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
- //TODO we can use initiator logic here
if (originatedFromMyself) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": saw proposal from mysql. ignoring");
} else if (transition(State.PROPOSED)) {
+ if (serverMsgId != null) {
+ this.message.setServerMsgId(serverMsgId);
+ }
+ this.message.setTime(timestamp);
startRinging();
} else {
Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring session proposal because already in " + state);
@@ -449,10 +464,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
xmppConnectionService.getNotificationService().showIncomingCallNotification(id);
}
- private void receiveProceed(final Jid from, final Element proceed) {
+ private void receiveProceed(final Jid from, final String serverMsgId, final long timestamp) {
if (from.equals(id.with)) {
if (isInitiator()) {
if (transition(State.PROCEED)) {
+ if (serverMsgId != null) {
+ this.message.setServerMsgId(serverMsgId);
+ }
+ this.message.setTime(timestamp);
this.sendSessionInitiate(State.SESSION_INITIALIZED_PRE_APPROVED);
} else {
Log.d(Config.LOGTAG, String.format("%s: ignoring proceed because already in %s", id.account.getJid().asBareJid(), this.state));
@@ -471,11 +490,15 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
}
- private void receiveRetract(final Jid from, final Element retract) {
+ private void receiveRetract(final Jid from, final String serverMsgId, final long timestamp) {
if (from.equals(id.with)) {
if (transition(State.RETRACTED)) {
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": session with " + id.with + " has been retracted");
+ if (serverMsgId != null) {
+ this.message.setServerMsgId(serverMsgId);
+ }
+ this.message.setTime(timestamp);
writeLogMessageMissed();
jingleConnectionManager.finishConnection(this);
} else {
@@ -729,6 +752,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private void rejectCallFromProposed() {
transitionOrThrow(State.REJECTED);
+ writeLogMessageMissed();
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
this.sendJingleMessage("reject");
jingleConnectionManager.finishConnection(this);
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index f353145b0..6aacd3673 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -907,6 +907,7 @@
Incoming call · %s
Outgoing call
Outgoing call · %s
+ Missed call
- View %1$d Participant
- View %1$d Participants