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