send and receive session terminates

This commit is contained in:
Daniel Gultsch 2020-04-08 15:27:17 +02:00
parent 00f273b0c0
commit 859bc0bef3
5 changed files with 74 additions and 44 deletions

View file

@ -164,6 +164,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
break; break;
case DECLINED_OR_BUSY: case DECLINED_OR_BUSY:
binding.status.setText(R.string.rtp_state_declined_or_busy); binding.status.setText(R.string.rtp_state_declined_or_busy);
break;
case CONNECTIVITY_ERROR: case CONNECTIVITY_ERROR:
binding.status.setText(R.string.rtp_state_connectivity_error); binding.status.setText(R.string.rtp_state_connectivity_error);
break; break;

View file

@ -90,6 +90,7 @@ public abstract class AbstractJingleConnection {
REJECTED, REJECTED,
RETRACTED, RETRACTED,
SESSION_INITIALIZED, //equal to 'PENDING' SESSION_INITIALIZED, //equal to 'PENDING'
SESSION_INITIALIZED_PRE_APPROVED,
SESSION_ACCEPTED, //equal to 'ACTIVE' SESSION_ACCEPTED, //equal to 'ACTIVE'
TERMINATED_SUCCESS, //equal to 'ENDED' (after successful call) ui will just close 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_DECLINED_OR_BUSY, //equal to 'ENDED' (after other party declined the call)

View file

@ -33,8 +33,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
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, State.REJECTED, State.RETRACTED)); transitionBuilder.put(State.PROPOSED, ImmutableList.of(State.ACCEPTED, State.PROCEED, State.REJECTED, State.RETRACTED));
transitionBuilder.put(State.PROCEED, ImmutableList.of(State.SESSION_INITIALIZED)); transitionBuilder.put(State.PROCEED, ImmutableList.of(State.SESSION_INITIALIZED_PRE_APPROVED));
transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(State.SESSION_ACCEPTED)); transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(State.SESSION_ACCEPTED, State.TERMINATED_CANCEL_OR_TIMEOUT));
transitionBuilder.put(State.SESSION_INITIALIZED_PRE_APPROVED, ImmutableList.of(State.SESSION_ACCEPTED, State.TERMINATED_CANCEL_OR_TIMEOUT));
transitionBuilder.put(State.SESSION_ACCEPTED, ImmutableList.of(State.TERMINATED_SUCCESS, State.TERMINATED_CONNECTIVITY_ERROR));
VALID_TRANSITIONS = transitionBuilder.build(); VALID_TRANSITIONS = transitionBuilder.build();
} }
@ -73,27 +75,33 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private void receiveSessionTerminate(final JinglePacket jinglePacket) { private void receiveSessionTerminate(final JinglePacket jinglePacket) {
final Reason reason = jinglePacket.getReason(); final Reason reason = jinglePacket.getReason();
switch (reason) { final State previous = this.state;
case SUCCESS: Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session terminate reason=" + reason + " while in state " + previous);
transitionOrThrow(State.TERMINATED_SUCCESS); webRTCWrapper.close();
break; transitionOrThrow(reasonToState(reason));
case DECLINE: if (previous == State.PROPOSED || previous == State.SESSION_INITIALIZED) {
case BUSY: xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
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); jingleConnectionManager.finishConnection(this);
} }
private static State reasonToState(Reason reason) {
switch (reason) {
case SUCCESS:
return State.TERMINATED_SUCCESS;
case DECLINE:
case BUSY:
return State.TERMINATED_DECLINED_OR_BUSY;
case CANCEL:
case TIMEOUT:
return State.TERMINATED_CANCEL_OR_TIMEOUT;
default:
return State.TERMINATED_CONNECTIVITY_ERROR;
}
}
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_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
final RtpContentMap contentMap; final RtpContentMap contentMap;
try { try {
contentMap = RtpContentMap.of(jinglePacket); contentMap = RtpContentMap.of(jinglePacket);
@ -142,10 +150,15 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return; return;
} }
Log.d(Config.LOGTAG, "processing session-init with " + contentMap.contents.size() + " contents"); Log.d(Config.LOGTAG, "processing session-init with " + contentMap.contents.size() + " contents");
final State oldState = this.state; final State target;
if (transition(State.SESSION_INITIALIZED)) { if (this.state == State.PROCEED) {
target = State.SESSION_INITIALIZED_PRE_APPROVED;
} else {
target = State.SESSION_INITIALIZED;
}
if (transition(target)) {
this.initiatorRtpContentMap = contentMap; this.initiatorRtpContentMap = contentMap;
if (oldState == State.PROCEED) { if (target == State.SESSION_INITIALIZED_PRE_APPROVED) {
Log.d(Config.LOGTAG, "automatically accepting"); Log.d(Config.LOGTAG, "automatically accepting");
sendSessionAccept(); sendSessionAccept();
} else { } else {
@ -297,7 +310,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
if (from.equals(id.with)) { if (from.equals(id.with)) {
if (isInitiator()) { if (isInitiator()) {
if (transition(State.PROCEED)) { if (transition(State.PROCEED)) {
this.sendSessionInitiate(); this.sendSessionInitiate(State.SESSION_INITIALIZED_PRE_APPROVED);
} else { } else {
Log.d(Config.LOGTAG, String.format("%s: ignoring proceed because already in %s", id.account.getJid().asBareJid(), this.state)); Log.d(Config.LOGTAG, String.format("%s: ignoring proceed because already in %s", id.account.getJid().asBareJid(), this.state));
} }
@ -331,7 +344,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
} }
private void sendSessionInitiate() { private void sendSessionInitiate(final State targetState) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate"); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate");
setupWebRTC(); setupWebRTC();
try { try {
@ -339,21 +352,31 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
Log.d(Config.LOGTAG, "description: " + webRTCSessionDescription.description); Log.d(Config.LOGTAG, "description: " + webRTCSessionDescription.description);
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
sendSessionInitiate(rtpContentMap); sendSessionInitiate(rtpContentMap, targetState);
this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
} catch (Exception e) { } catch (Exception e) {
Log.d(Config.LOGTAG, "unable to sendSessionInitiate", e); Log.d(Config.LOGTAG, "unable to sendSessionInitiate", e);
} }
} }
private void sendSessionInitiate(RtpContentMap rtpContentMap) { private void sendSessionInitiate(RtpContentMap rtpContentMap, final State targetState) {
this.initiatorRtpContentMap = rtpContentMap; this.initiatorRtpContentMap = rtpContentMap;
this.transitionOrThrow(State.SESSION_INITIALIZED); this.transitionOrThrow(targetState);
final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
Log.d(Config.LOGTAG, sessionInitiate.toString()); Log.d(Config.LOGTAG, sessionInitiate.toString());
send(sessionInitiate); send(sessionInitiate);
} }
private void sendSessionTerminate(final Reason reason) {
final State target = reasonToState(reason);
transitionOrThrow(target);
final JinglePacket jinglePacket = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
jinglePacket.setReason(reason);
send(jinglePacket);
Log.d(Config.LOGTAG, jinglePacket.toString());
jingleConnectionManager.finishConnection(this);
}
private void sendTransportInfo(final String contentName, IceUdpTransportInfo.Candidate candidate) { private void sendTransportInfo(final String contentName, IceUdpTransportInfo.Candidate candidate) {
final RtpContentMap transportInfo; final RtpContentMap transportInfo;
try { try {
@ -377,6 +400,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
public RtpEndUserState getEndUserState() { public RtpEndUserState getEndUserState() {
switch (this.state) { switch (this.state) {
case PROPOSED: case PROPOSED:
case SESSION_INITIALIZED:
if (isInitiator()) { if (isInitiator()) {
return RtpEndUserState.RINGING; return RtpEndUserState.RINGING;
} else { } else {
@ -388,7 +412,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} else { } else {
return RtpEndUserState.ACCEPTING_CALL; return RtpEndUserState.ACCEPTING_CALL;
} }
case SESSION_INITIALIZED: case SESSION_INITIALIZED_PRE_APPROVED:
return RtpEndUserState.CONNECTING; return RtpEndUserState.CONNECTING;
case SESSION_ACCEPTED: case SESSION_ACCEPTED:
final PeerConnection.PeerConnectionState state = webRTCWrapper.getState(); final PeerConnection.PeerConnectionState state = webRTCWrapper.getState();
@ -446,20 +470,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
public void endCall() { public void endCall() {
if (isInitiator() && isInState(State.SESSION_INITIALIZED)) {
//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(); webRTCWrapper.close();
sendSessionTerminate(Reason.CANCEL);
} else if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
webRTCWrapper.close();
sendSessionTerminate(Reason.SUCCESS);
} else { } else {
//TODO during earlier stages we want to retract the proposal etc throw new IllegalStateException("called 'endCall' while in state " + this.state);
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": called 'endCall' while in state " + this.state);
} }
} }
@ -530,9 +548,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
@Override @Override
public void onConnectionChange(PeerConnection.PeerConnectionState newState) { public void onConnectionChange(final 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();
if (newState == PeerConnection.PeerConnectionState.FAILED) { //TODO guard this in isState(initiated,initated_approved,accepted) otherwise it might fire too late
sendSessionTerminate(Reason.CONNECTIVITY_ERROR);
}
} }
private void updateEndUserState() { private void updateEndUserState() {

View file

@ -207,10 +207,17 @@ public class WebRTCWrapper {
this.peerConnection = peerConnection; this.peerConnection = peerConnection;
} }
public void close() { public void closeOrThrow() {
requirePeerConnection().close(); requirePeerConnection().close();
} }
public void close() {
final PeerConnection peerConnection = this.peerConnection;
if (peerConnection != null) {
peerConnection.close();
}
}
public ListenableFuture<SessionDescription> createOffer() { public ListenableFuture<SessionDescription> createOffer() {
return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> { return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {

View file

@ -83,7 +83,7 @@ public class JinglePacket extends IqPacket {
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(new Element("reason").addChild(reason.toString())); jingle.addChild("reason").addChild(reason.toString());
} }
//RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise //RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise