prepare more state transitions

This commit is contained in:
Daniel Gultsch 2020-04-07 21:26:51 +02:00
parent ccfc55e9b6
commit e2f1cec2e5
12 changed files with 157 additions and 52 deletions

View file

@ -289,7 +289,8 @@
<activity <activity
android:name=".ui.ChannelDiscoveryActivity" android:name=".ui.ChannelDiscoveryActivity"
android:label="@string/discover_channels" /> android:label="@string/discover_channels" />
<activity android:name=".ui.RtpSessionActivity" /> <activity android:name=".ui.RtpSessionActivity"
android:launchMode="singleTop"/>
</application> </application>
</manifest> </manifest>

View file

@ -105,7 +105,7 @@ public final class Config {
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb 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 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 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 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 public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption

View file

@ -364,8 +364,8 @@ public class NotificationService {
fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString());
fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString());
fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
return PendingIntent.getActivity(mXmppConnectionService, requestCode, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT); return PendingIntent.getActivity(mXmppConnectionService, requestCode, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
} }

View file

@ -643,6 +643,7 @@ public class XmppConnectionService extends Service {
case ACTION_DISMISS_CALL: case ACTION_DISMISS_CALL:
final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID); final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID);
Log.d(Config.LOGTAG,"received intent to dismiss call with session id "+sessionId); Log.d(Config.LOGTAG,"received intent to dismiss call with session id "+sessionId);
mJingleConnectionManager.rejectRtpSession(sessionId);
break; break;
case ACTION_DISMISS_ERROR_NOTIFICATIONS: case ACTION_DISMISS_ERROR_NOTIFICATIONS:
dismissErrorNotifications(); dismissErrorNotifications();

View file

@ -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 @Override
void onBackendConnected() { void onBackendConnected() {
final Intent intent = getIntent(); final Intent intent = getIntent();
@ -150,6 +159,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
public void onJingleRtpConnectionUpdate(Account account, Jid with, RtpEndUserState state) { public void onJingleRtpConnectionUpdate(Account account, Jid with, RtpEndUserState state) {
final AbstractJingleConnection.Id id = requireRtpConnection().getId(); final AbstractJingleConnection.Id id = requireRtpConnection().getId();
if (account == id.account && id.with.equals(with)) { if (account == id.account && id.with.equals(with)) {
if (state == RtpEndUserState.ENDED) {
finish();
return;
}
runOnUiThread(() -> { runOnUiThread(() -> {
updateStateDisplay(state); updateStateDisplay(state);
updateButtonConfiguration(state); updateButtonConfiguration(state);

View file

@ -87,8 +87,13 @@ public abstract class AbstractJingleConnection {
PROPOSED, PROPOSED,
ACCEPTED, ACCEPTED,
PROCEED, PROCEED,
REJECTED,
RETRACTED,
SESSION_INITIALIZED, //equal to 'PENDING' SESSION_INITIALIZED, //equal to 'PENDING'
SESSION_ACCEPTED, //equal to 'ACTIVE' 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
} }
} }

View file

@ -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 { public static class RtpSessionProposal {
private final Account account; private final Account account;
public final Jid with; public final Jid with;

View file

@ -156,7 +156,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
@Override @Override
public void onFileTransferAborted() { public void onFileTransferAborted() {
JingleFileTransferConnection.this.sendSessionTerminate("connectivity-error"); JingleFileTransferConnection.this.sendSessionTerminate(Reason.CONNECTIVITY_ERROR);
JingleFileTransferConnection.this.fail(); JingleFileTransferConnection.this.fail();
} }
}; };
@ -245,23 +245,20 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
if (action == JinglePacket.Action.SESSION_INITIATE) { if (action == JinglePacket.Action.SESSION_INITIATE) {
init(packet); init(packet);
} else if (action == JinglePacket.Action.SESSION_TERMINATE) { } else if (action == JinglePacket.Action.SESSION_TERMINATE) {
Reason reason = packet.getReason(); final Reason reason = packet.getReason();
if (reason != null) { switch (reason) {
if (reason.hasChild("cancel")) { case CANCEL:
this.cancelled = true; this.cancelled = true;
this.fail(); this.fail();
} else if (reason.hasChild("success")) { break;
case SUCCESS:
this.receiveSuccess(); this.receiveSuccess();
} else { break;
final List<Element> children = reason.getChildren(); default:
if (children.size() == 1) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session-terminate with reason " + reason);
this.fail(children.get(0).getName()); this.fail();
} else { break;
this.fail();
}
}
} else {
this.fail();
} }
} else if (action == JinglePacket.Action.SESSION_ACCEPT) { } else if (action == JinglePacket.Action.SESSION_ACCEPT) {
receiveAccept(packet); receiveAccept(packet);
@ -756,7 +753,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
connection.setActivated(true); connection.setActivated(true);
} else { } else {
Log.d(Config.LOGTAG, "activated connection not found"); Log.d(Config.LOGTAG, "activated connection not found");
sendSessionTerminate("failed-transport"); sendSessionTerminate(Reason.FAILED_TRANSPORT);
this.fail(); this.fail();
} }
} }
@ -894,7 +891,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
} }
private void sendSuccess() { private void sendSuccess() {
sendSessionTerminate("success"); sendSessionTerminate(Reason.SUCCESS);
this.disconnectSocks5Connections(); this.disconnectSocks5Connections();
this.mJingleStatus = JINGLE_STATUS_FINISHED; this.mJingleStatus = JINGLE_STATUS_FINISHED;
this.message.setStatus(Message.STATUS_RECEIVED); this.message.setStatus(Message.STATUS_RECEIVED);
@ -1014,10 +1011,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
@Override @Override
public void cancel() { public void cancel() {
this.cancelled = true; this.cancelled = true;
abort("cancel"); abort(Reason.CANCEL);
} }
void abort(final String reason) { void abort(final Reason reason) {
this.disconnectSocks5Connections(); this.disconnectSocks5Connections();
if (this.transport instanceof JingleInBandTransport) { if (this.transport instanceof JingleInBandTransport) {
this.transport.disconnect(); this.transport.disconnect();
@ -1065,11 +1062,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
this.jingleConnectionManager.finishConnection(this); this.jingleConnectionManager.finishConnection(this);
} }
private void sendSessionTerminate(String reason) { private void sendSessionTerminate(Reason reason) {
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_TERMINATE); final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_TERMINATE);
final Reason r = new Reason(); packet.setReason(reason);
r.addChild(reason);
packet.setReason(r);
this.sendJinglePacket(packet); this.sendJinglePacket(packet);
} }

View file

@ -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.Group;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
@ -33,7 +34,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
static { static {
final ImmutableMap.Builder<State, Collection<State>> transitionBuilder = new ImmutableMap.Builder<>(); final ImmutableMap.Builder<State, Collection<State>> transitionBuilder = new ImmutableMap.Builder<>();
transitionBuilder.put(State.NULL, ImmutableList.of(State.PROPOSED, State.SESSION_INITIALIZED)); 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.PROCEED, ImmutableList.of(State.SESSION_INITIALIZED));
transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(State.SESSION_ACCEPTED)); transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(State.SESSION_ACCEPTED));
VALID_TRANSITIONS = transitionBuilder.build(); VALID_TRANSITIONS = transitionBuilder.build();
@ -63,12 +64,36 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
case SESSION_ACCEPT: case SESSION_ACCEPT:
receiveSessionAccept(jinglePacket); receiveSessionAccept(jinglePacket);
break; break;
case SESSION_TERMINATE:
receiveSessionTerminate(jinglePacket);
break;
default: default:
Log.d(Config.LOGTAG, String.format("%s: received unhandled jingle action %s", id.account.getJid().asBareJid(), jinglePacket.getAction())); Log.d(Config.LOGTAG, String.format("%s: received unhandled jingle action %s", id.account.getJid().asBareJid(), jinglePacket.getAction()));
break; 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) { private void receiveTransportInfo(final JinglePacket jinglePacket) {
if (isInState(State.SESSION_INITIALIZED, State.SESSION_ACCEPTED)) { if (isInState(State.SESSION_INITIALIZED, State.SESSION_ACCEPTED)) {
final RtpContentMap contentMap; final RtpContentMap contentMap;
@ -315,10 +340,24 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} else if (state == PeerConnection.PeerConnectionState.CLOSED) { } else if (state == PeerConnection.PeerConnectionState.CLOSED) {
return RtpEndUserState.ENDING_CALL; return RtpEndUserState.ENDING_CALL;
} else { } 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(); acceptCallFromSessionInitialized();
break; break;
default: 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() { public void rejectCall() {
Log.d(Config.LOGTAG, "todo rejecting call"); switch (this.state) {
xmppConnectionService.getNotificationService().cancelIncomingCallNotification(); case PROPOSED:
rejectCallFromProposed();
break;
default:
throw new IllegalStateException("Can not reject call from " + this.state);
}
} }
public void endCall() { public void endCall() {
//TODO from `propose` we call `retract`
if (isInState(State.SESSION_INITIALIZED, State.SESSION_ACCEPTED)) { 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(); webRTCWrapper.close();
} else { } 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); 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() { private void acceptCallFromProposed() {
transitionOrThrow(State.PROCEED); transitionOrThrow(State.PROCEED);
xmppConnectionService.getNotificationService().cancelIncomingCallNotification(); 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(); final MessagePacket messagePacket = new MessagePacket();
messagePacket.setTo(id.with); messagePacket.setTo(id.with);
//Note that Movim needs 'accept', correct is 'proceed' https://github.com/movim/movim/issues/916 messagePacket.addChild(action, Namespace.JINGLE_MESSAGE).setAttribute("id", id.sessionId);
messagePacket.addChild("proceed", Namespace.JINGLE_MESSAGE).setAttribute("id", id.sessionId);
Log.d(Config.LOGTAG, messagePacket.toString()); Log.d(Config.LOGTAG, messagePacket.toString());
xmppConnectionService.sendMessagePacket(id.account, messagePacket); xmppConnectionService.sendMessagePacket(id.account, messagePacket);
} }
@ -400,7 +467,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
@Override @Override
public void onConnectionChange(PeerConnection.PeerConnectionState newState) { 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(); updateEndUserState();
} }

View file

@ -9,5 +9,7 @@ public enum RtpEndUserState {
ACCEPTED_ON_OTHER_DEVICE, //received 'accept' from one of our own devices 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 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 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
} }

View file

@ -70,12 +70,20 @@ public class JinglePacket extends IqPacket {
public Reason getReason() { public Reason getReason() {
final Element reason = getJingleChild("reason"); 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) { public void setReason(final Reason reason) {
final Element jingle = findChild("jingle", Namespace.JINGLE); 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 //RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise

View file

@ -1,20 +1,23 @@
package eu.siacs.conversations.xmpp.jingle.stanzas; 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() { public static Reason of(final String value) {
super("reason"); try {
return Reason.valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, value));
} catch (Exception e) {
return UNKNOWN;
}
} }
public static Reason upgrade(final Element element) { @Override
Preconditions.checkArgument("reason".equals(element.getName())); @NonNull
final Reason reason = new Reason(); public String toString() {
reason.setAttributes(element.getAttributes()); return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString());
reason.setChildren(element.getChildren());
return reason;
} }
} }