From e2f1cec2e541501400ec1e86d976a700b06bf194 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 7 Apr 2020 21:26:51 +0200 Subject: [PATCH] prepare more state transitions --- src/main/AndroidManifest.xml | 3 +- .../java/eu/siacs/conversations/Config.java | 2 +- .../services/NotificationService.java | 4 +- .../services/XmppConnectionService.java | 1 + .../conversations/ui/RtpSessionActivity.java | 13 +++ .../xmpp/jingle/AbstractJingleConnection.java | 7 +- .../xmpp/jingle/JingleConnectionManager.java | 10 +++ .../jingle/JingleFileTransferConnection.java | 41 ++++----- .../xmpp/jingle/JingleRtpConnection.java | 85 +++++++++++++++++-- .../xmpp/jingle/RtpEndUserState.java | 4 +- .../xmpp/jingle/stanzas/JinglePacket.java | 12 ++- .../xmpp/jingle/stanzas/Reason.java | 27 +++--- 12 files changed, 157 insertions(+), 52 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index dac289003..9ab4073b2 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -289,7 +289,8 @@ - + diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index d12de90b7..777953735 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -105,7 +105,7 @@ public final class Config { public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true; - public static final boolean DISABLE_HTTP_UPLOAD = true; + public static final boolean DISABLE_HTTP_UPLOAD = false; public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts public static final boolean BACKGROUND_STANZA_LOGGING = false; //log all stanzas that were received while the app is in background public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 8a521ee9d..9a51393a9 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -364,8 +364,8 @@ public class NotificationService { fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); - fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + //fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); return PendingIntent.getActivity(mXmppConnectionService, requestCode, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 1ac96354e..22df52035 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -643,6 +643,7 @@ public class XmppConnectionService extends Service { case ACTION_DISMISS_CALL: final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID); Log.d(Config.LOGTAG,"received intent to dismiss call with session id "+sessionId); + mJingleConnectionManager.rejectRtpSession(sessionId); break; case ACTION_DISMISS_ERROR_NOTIFICATIONS: dismissErrorNotifications(); diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 2464f81b7..5778a3de9 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -70,6 +70,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } + @Override + public void onNewIntent(final Intent intent) { + super.onNewIntent(intent); + if (ACTION_ACCEPT.equals(intent.getAction())) { + Log.d(Config.LOGTAG,"accepting through onNewIntent()"); + requireRtpConnection().acceptCall(); + } + } + @Override void onBackendConnected() { final Intent intent = getIntent(); @@ -150,6 +159,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe public void onJingleRtpConnectionUpdate(Account account, Jid with, RtpEndUserState state) { final AbstractJingleConnection.Id id = requireRtpConnection().getId(); if (account == id.account && id.with.equals(with)) { + if (state == RtpEndUserState.ENDED) { + finish(); + return; + } runOnUiThread(() -> { updateStateDisplay(state); updateButtonConfiguration(state); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java index 603371394..5b60479b8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java @@ -87,8 +87,13 @@ public abstract class AbstractJingleConnection { PROPOSED, ACCEPTED, PROCEED, + REJECTED, + RETRACTED, SESSION_INITIALIZED, //equal to 'PENDING' SESSION_ACCEPTED, //equal to 'ACTIVE' - TERMINATED //equal to 'ENDED' + TERMINATED_SUCCESS, //equal to 'ENDED' (after successful call) ui will just close + TERMINATED_DECLINED_OR_BUSY, //equal to 'ENDED' (after other party declined the call) + TERMINATED_CONNECTIVITY_ERROR, //equal to 'ENDED' (but after network failures; ui will display retry button) + TERMINATED_CANCEL_OR_TIMEOUT //more or less the same as retracted; caller pressed end call before session was accepted } } 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 982dc00b4..404cb0cc4 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -271,6 +271,16 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } + public void rejectRtpSession(final String sessionId) { + for(final AbstractJingleConnection connection : this.connections.values()) { + if (connection.getId().sessionId.equals(sessionId)) { + if (connection instanceof JingleRtpConnection) { + ((JingleRtpConnection) connection).rejectCall(); + } + } + } + } + public static class RtpSessionProposal { private final Account account; public final Jid with; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 68964d131..e309e4289 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -156,7 +156,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple @Override public void onFileTransferAborted() { - JingleFileTransferConnection.this.sendSessionTerminate("connectivity-error"); + JingleFileTransferConnection.this.sendSessionTerminate(Reason.CONNECTIVITY_ERROR); JingleFileTransferConnection.this.fail(); } }; @@ -245,23 +245,20 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple if (action == JinglePacket.Action.SESSION_INITIATE) { init(packet); } else if (action == JinglePacket.Action.SESSION_TERMINATE) { - Reason reason = packet.getReason(); - if (reason != null) { - if (reason.hasChild("cancel")) { + final Reason reason = packet.getReason(); + switch (reason) { + case CANCEL: this.cancelled = true; this.fail(); - } else if (reason.hasChild("success")) { + break; + case SUCCESS: this.receiveSuccess(); - } else { - final List children = reason.getChildren(); - if (children.size() == 1) { - this.fail(children.get(0).getName()); - } else { - this.fail(); - } - } - } else { - this.fail(); + break; + default: + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session-terminate with reason " + reason); + this.fail(); + break; + } } else if (action == JinglePacket.Action.SESSION_ACCEPT) { receiveAccept(packet); @@ -756,7 +753,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple connection.setActivated(true); } else { Log.d(Config.LOGTAG, "activated connection not found"); - sendSessionTerminate("failed-transport"); + sendSessionTerminate(Reason.FAILED_TRANSPORT); this.fail(); } } @@ -894,7 +891,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } private void sendSuccess() { - sendSessionTerminate("success"); + sendSessionTerminate(Reason.SUCCESS); this.disconnectSocks5Connections(); this.mJingleStatus = JINGLE_STATUS_FINISHED; this.message.setStatus(Message.STATUS_RECEIVED); @@ -1014,10 +1011,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple @Override public void cancel() { this.cancelled = true; - abort("cancel"); + abort(Reason.CANCEL); } - void abort(final String reason) { + void abort(final Reason reason) { this.disconnectSocks5Connections(); if (this.transport instanceof JingleInBandTransport) { this.transport.disconnect(); @@ -1065,11 +1062,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple this.jingleConnectionManager.finishConnection(this); } - private void sendSessionTerminate(String reason) { + private void sendSessionTerminate(Reason reason) { final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_TERMINATE); - final Reason r = new Reason(); - r.addChild(reason); - packet.setReason(r); + packet.setReason(reason); this.sendJinglePacket(packet); } 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 56991635f..9b73b131d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -23,6 +23,7 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import rocks.xmpp.addr.Jid; @@ -33,7 +34,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web static { final ImmutableMap.Builder> transitionBuilder = new ImmutableMap.Builder<>(); transitionBuilder.put(State.NULL, ImmutableList.of(State.PROPOSED, State.SESSION_INITIALIZED)); - transitionBuilder.put(State.PROPOSED, ImmutableList.of(State.ACCEPTED, State.PROCEED)); + transitionBuilder.put(State.PROPOSED, ImmutableList.of(State.ACCEPTED, State.PROCEED, State.REJECTED)); transitionBuilder.put(State.PROCEED, ImmutableList.of(State.SESSION_INITIALIZED)); transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(State.SESSION_ACCEPTED)); VALID_TRANSITIONS = transitionBuilder.build(); @@ -63,12 +64,36 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web case SESSION_ACCEPT: receiveSessionAccept(jinglePacket); break; + case SESSION_TERMINATE: + receiveSessionTerminate(jinglePacket); + break; default: Log.d(Config.LOGTAG, String.format("%s: received unhandled jingle action %s", id.account.getJid().asBareJid(), jinglePacket.getAction())); break; } } + private void receiveSessionTerminate(final JinglePacket jinglePacket) { + final Reason reason = jinglePacket.getReason(); + switch (reason) { + case SUCCESS: + transitionOrThrow(State.TERMINATED_SUCCESS); + break; + case DECLINE: + case BUSY: + transitionOrThrow(State.TERMINATED_DECLINED_OR_BUSY); + break; + case CANCEL: + case TIMEOUT: + transitionOrThrow(State.TERMINATED_CANCEL_OR_TIMEOUT); + break; + default: + transitionOrThrow(State.TERMINATED_CONNECTIVITY_ERROR); + break; + } + jingleConnectionManager.finishConnection(this); + } + private void receiveTransportInfo(final JinglePacket jinglePacket) { if (isInState(State.SESSION_INITIALIZED, State.SESSION_ACCEPTED)) { final RtpContentMap contentMap; @@ -315,10 +340,24 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } else if (state == PeerConnection.PeerConnectionState.CLOSED) { return RtpEndUserState.ENDING_CALL; } else { - return RtpEndUserState.FAILED; + return RtpEndUserState.ENDING_CALL; } + case REJECTED: + case TERMINATED_DECLINED_OR_BUSY: + if (isInitiator()) { + return RtpEndUserState.DECLINED_OR_BUSY; + } else { + return RtpEndUserState.ENDED; + } + case TERMINATED_SUCCESS: + case ACCEPTED: + case RETRACTED: + case TERMINATED_CANCEL_OR_TIMEOUT: + return RtpEndUserState.ENDED; + case TERMINATED_CONNECTIVITY_ERROR: + return RtpEndUserState.CONNECTIVITY_ERROR; } - return RtpEndUserState.FAILED; + throw new IllegalStateException(String.format("%s has no equivalent EndUserState", this.state)); } @@ -331,19 +370,34 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web acceptCallFromSessionInitialized(); break; default: - throw new IllegalStateException("Can not pick up call from " + this.state); + throw new IllegalStateException("Can not accept call from " + this.state); } } public void rejectCall() { - Log.d(Config.LOGTAG, "todo rejecting call"); - xmppConnectionService.getNotificationService().cancelIncomingCallNotification(); + switch (this.state) { + case PROPOSED: + rejectCallFromProposed(); + break; + default: + throw new IllegalStateException("Can not reject call from " + this.state); + } } public void endCall() { + + //TODO from `propose` we call `retract` + if (isInState(State.SESSION_INITIALIZED, State.SESSION_ACCEPTED)) { + //TODO during session_initialized we might not have a peer connection yet (if the session was initialized directly) + + //TODO from session_initialized we call `cancel` + + //TODO from session_accepted we call `success` + webRTCWrapper.close(); } else { + //TODO during earlier stages we want to retract the proposal etc Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": called 'endCall' while in state " + this.state); } } @@ -356,10 +410,23 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void acceptCallFromProposed() { transitionOrThrow(State.PROCEED); xmppConnectionService.getNotificationService().cancelIncomingCallNotification(); + //Note that Movim needs 'accept', correct is 'proceed' https://github.com/movim/movim/issues/916 + this.sendJingleMessage("proceed"); + + //TODO send `accept` to self + } + + private void rejectCallFromProposed() { + transitionOrThrow(State.REJECTED); + xmppConnectionService.getNotificationService().cancelIncomingCallNotification(); + this.sendJingleMessage("reject"); + jingleConnectionManager.finishConnection(this); + } + + private void sendJingleMessage(final String action) { final MessagePacket messagePacket = new MessagePacket(); messagePacket.setTo(id.with); - //Note that Movim needs 'accept', correct is 'proceed' https://github.com/movim/movim/issues/916 - messagePacket.addChild("proceed", Namespace.JINGLE_MESSAGE).setAttribute("id", id.sessionId); + messagePacket.addChild(action, Namespace.JINGLE_MESSAGE).setAttribute("id", id.sessionId); Log.d(Config.LOGTAG, messagePacket.toString()); xmppConnectionService.sendMessagePacket(id.account, messagePacket); } @@ -400,7 +467,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onConnectionChange(PeerConnection.PeerConnectionState newState) { - Log.d(Config.LOGTAG,id.account.getJid().asBareJid()+": PeerConnectionState changed to "+newState); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to " + newState); updateEndUserState(); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java index 30055a3d6..e8d8bd5d2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java @@ -9,5 +9,7 @@ public enum RtpEndUserState { ACCEPTED_ON_OTHER_DEVICE, //received 'accept' from one of our own devices ACCEPTING_CALL, //'proceed' message has been sent; but no session-initiate has been received ENDING_CALL, //libwebrt says 'closed' but session-terminate hasnt gone through - FAILED //something went wrong. TODO needs more concrete error states + ENDED, //close UI + DECLINED_OR_BUSY, //other party declined; no retry button + CONNECTIVITY_ERROR //network error; retry button } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java index 3b4ef47d1..25ca3d5fd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java @@ -70,12 +70,20 @@ public class JinglePacket extends IqPacket { public Reason getReason() { final Element reason = getJingleChild("reason"); - return reason == null ? null : Reason.upgrade(reason); + if (reason == null) { + return Reason.UNKNOWN; + } + for(Element child : reason.getChildren()) { + if (!"text".equals(child.getName())) { + return Reason.of(child.getName()); + } + } + return Reason.UNKNOWN; } public void setReason(final Reason reason) { final Element jingle = findChild("jingle", Namespace.JINGLE); - jingle.addChild(reason); + jingle.addChild(new Element("reason").addChild(reason.toString())); } //RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java index 1eae65c5b..070f77226 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java @@ -1,20 +1,23 @@ package eu.siacs.conversations.xmpp.jingle.stanzas; -import com.google.common.base.Preconditions; +import android.support.annotation.NonNull; -import eu.siacs.conversations.xml.Element; +import com.google.common.base.CaseFormat; -public class Reason extends Element { +public enum Reason { + SUCCESS, DECLINE, BUSY, CANCEL, CONNECTIVITY_ERROR, FAILED_TRANSPORT, TIMEOUT, UNKNOWN; - public Reason() { - super("reason"); + public static Reason of(final String value) { + try { + return Reason.valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, value)); + } catch (Exception e) { + return UNKNOWN; + } } - public static Reason upgrade(final Element element) { - Preconditions.checkArgument("reason".equals(element.getName())); - final Reason reason = new Reason(); - reason.setAttributes(element.getAttributes()); - reason.setChildren(element.getChildren()); - return reason; + @Override + @NonNull + public String toString() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString()); } -} +} \ No newline at end of file