proper iq tracing (handling of errors); responding to all iqs
This commit is contained in:
parent
15a2491d7b
commit
268eedad89
|
@ -169,6 +169,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
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;
|
||||||
|
case APPLICATION_ERROR:
|
||||||
|
binding.status.setText(R.string.rtp_state_application_failure);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +194,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
this.binding.endCall.setImageResource(R.drawable.ic_clear_white_48dp);
|
this.binding.endCall.setImageResource(R.drawable.ic_clear_white_48dp);
|
||||||
this.binding.endCall.show();
|
this.binding.endCall.show();
|
||||||
this.binding.acceptCall.hide();
|
this.binding.acceptCall.hide();
|
||||||
} else if (state == RtpEndUserState.CONNECTIVITY_ERROR) {
|
} else if (state == RtpEndUserState.CONNECTIVITY_ERROR || state == RtpEndUserState.APPLICATION_ERROR) {
|
||||||
this.binding.rejectCall.setOnClickListener(this::exit);
|
this.binding.rejectCall.setOnClickListener(this::exit);
|
||||||
this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
|
this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
|
||||||
this.binding.rejectCall.show();
|
this.binding.rejectCall.show();
|
||||||
|
|
|
@ -95,6 +95,7 @@ public abstract class AbstractJingleConnection {
|
||||||
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)
|
||||||
TERMINATED_CONNECTIVITY_ERROR, //equal to 'ENDED' (but after network failures; ui will display retry button)
|
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
|
TERMINATED_CANCEL_OR_TIMEOUT, //more or less the same as retracted; caller pressed end call before session was accepted
|
||||||
|
TERMINATED_APPLICATION_FAILURE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,22 +55,27 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
} else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace)) {
|
} else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace)) {
|
||||||
connection = new JingleRtpConnection(this, id, from);
|
connection = new JingleRtpConnection(this, id, from);
|
||||||
} else {
|
} else {
|
||||||
//TODO return feature-not-implemented
|
respondWithJingleError(account, packet, "unsupported-info", "feature-not-implemented", "cancel");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
connections.put(id, connection);
|
connections.put(id, connection);
|
||||||
connection.deliverPacket(packet);
|
connection.deliverPacket(packet);
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, "unable to route jingle packet: " + packet);
|
Log.d(Config.LOGTAG, "unable to route jingle packet: " + packet);
|
||||||
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel");
|
||||||
final Element error = response.addChild("error");
|
|
||||||
error.setAttribute("type", "cancel");
|
|
||||||
error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
|
||||||
error.addChild("unknown-session", "urn:xmpp:jingle:errors:1");
|
|
||||||
account.getXmppConnection().sendIqPacket(response, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void respondWithJingleError(final Account account, final IqPacket original, String jingleCondition, String condition, String conditionType) {
|
||||||
|
final IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR);
|
||||||
|
final Element error = response.addChild("error");
|
||||||
|
error.setAttribute("type", conditionType);
|
||||||
|
error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
|
error.addChild(jingleCondition, "urn:xmpp:jingle:errors:1");
|
||||||
|
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) {
|
||||||
Preconditions.checkArgument(Namespace.JINGLE_MESSAGE.equals(message.getNamespace()));
|
Preconditions.checkArgument(Namespace.JINGLE_MESSAGE.equals(message.getNamespace()));
|
||||||
final String sessionId = message.getAttribute("id");
|
final String sessionId = message.getAttribute("id");
|
||||||
|
|
|
@ -34,12 +34,40 @@ 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(
|
||||||
transitionBuilder.put(State.PROPOSED, ImmutableList.of(State.ACCEPTED, State.PROCEED, State.REJECTED, State.RETRACTED));
|
State.PROPOSED,
|
||||||
transitionBuilder.put(State.PROCEED, ImmutableList.of(State.SESSION_INITIALIZED_PRE_APPROVED, State.TERMINATED_SUCCESS));
|
State.SESSION_INITIALIZED,
|
||||||
transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(State.SESSION_ACCEPTED, State.TERMINATED_CANCEL_OR_TIMEOUT, State.TERMINATED_DECLINED_OR_BUSY));
|
State.TERMINATED_APPLICATION_FAILURE
|
||||||
transitionBuilder.put(State.SESSION_INITIALIZED_PRE_APPROVED, ImmutableList.of(State.SESSION_ACCEPTED, State.TERMINATED_CANCEL_OR_TIMEOUT, State.TERMINATED_DECLINED_OR_BUSY));
|
));
|
||||||
transitionBuilder.put(State.SESSION_ACCEPTED, ImmutableList.of(State.TERMINATED_SUCCESS, State.TERMINATED_CONNECTIVITY_ERROR));
|
transitionBuilder.put(State.PROPOSED, ImmutableList.of(
|
||||||
|
State.ACCEPTED,
|
||||||
|
State.PROCEED,
|
||||||
|
State.REJECTED,
|
||||||
|
State.RETRACTED,
|
||||||
|
State.TERMINATED_APPLICATION_FAILURE
|
||||||
|
));
|
||||||
|
transitionBuilder.put(State.PROCEED, ImmutableList.of(
|
||||||
|
State.SESSION_INITIALIZED_PRE_APPROVED,
|
||||||
|
State.TERMINATED_SUCCESS,
|
||||||
|
State.TERMINATED_APPLICATION_FAILURE
|
||||||
|
));
|
||||||
|
transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(
|
||||||
|
State.SESSION_ACCEPTED,
|
||||||
|
State.TERMINATED_CANCEL_OR_TIMEOUT,
|
||||||
|
State.TERMINATED_DECLINED_OR_BUSY,
|
||||||
|
State.TERMINATED_APPLICATION_FAILURE
|
||||||
|
));
|
||||||
|
transitionBuilder.put(State.SESSION_INITIALIZED_PRE_APPROVED, ImmutableList.of(
|
||||||
|
State.SESSION_ACCEPTED,
|
||||||
|
State.TERMINATED_CANCEL_OR_TIMEOUT,
|
||||||
|
State.TERMINATED_DECLINED_OR_BUSY,
|
||||||
|
State.TERMINATED_APPLICATION_FAILURE
|
||||||
|
));
|
||||||
|
transitionBuilder.put(State.SESSION_ACCEPTED, ImmutableList.of(
|
||||||
|
State.TERMINATED_SUCCESS,
|
||||||
|
State.TERMINATED_CONNECTIVITY_ERROR,
|
||||||
|
State.TERMINATED_APPLICATION_FAILURE
|
||||||
|
));
|
||||||
VALID_TRANSITIONS = transitionBuilder.build();
|
VALID_TRANSITIONS = transitionBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +92,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
case CANCEL:
|
case CANCEL:
|
||||||
case TIMEOUT:
|
case TIMEOUT:
|
||||||
return State.TERMINATED_CANCEL_OR_TIMEOUT;
|
return State.TERMINATED_CANCEL_OR_TIMEOUT;
|
||||||
|
case FAILED_APPLICATION:
|
||||||
|
return State.TERMINATED_APPLICATION_FAILURE;
|
||||||
default:
|
default:
|
||||||
return State.TERMINATED_CONNECTIVITY_ERROR;
|
return State.TERMINATED_CONNECTIVITY_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -86,12 +116,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
receiveSessionTerminate(jinglePacket);
|
receiveSessionTerminate(jinglePacket);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
respondOk(jinglePacket);
|
||||||
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) {
|
private void receiveSessionTerminate(final JinglePacket jinglePacket) {
|
||||||
|
respondOk(jinglePacket);
|
||||||
final Reason reason = jinglePacket.getReason();
|
final Reason reason = jinglePacket.getReason();
|
||||||
final State previous = this.state;
|
final State previous = this.state;
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session terminate reason=" + reason + " while in state " + previous);
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session terminate reason=" + reason + " while in state " + previous);
|
||||||
|
@ -105,11 +137,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
|
|
||||||
private void receiveTransportInfo(final JinglePacket jinglePacket) {
|
private void receiveTransportInfo(final JinglePacket jinglePacket) {
|
||||||
if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
|
if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
|
||||||
|
respondOk(jinglePacket);
|
||||||
final RtpContentMap contentMap;
|
final RtpContentMap contentMap;
|
||||||
try {
|
try {
|
||||||
contentMap = RtpContentMap.of(jinglePacket);
|
contentMap = RtpContentMap.of(jinglePacket);
|
||||||
} catch (IllegalArgumentException | NullPointerException e) {
|
} catch (IllegalArgumentException | NullPointerException e) {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", e);
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents; ignoring", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final RtpContentMap rtpContentMap = isInitiator() ? this.responderRtpContentMap : this.initiatorRtpContentMap;
|
final RtpContentMap rtpContentMap = isInitiator() ? this.responderRtpContentMap : this.initiatorRtpContentMap;
|
||||||
|
@ -136,13 +169,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received transport info while in state=" + this.state);
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received transport info while in state=" + this.state);
|
||||||
|
respondWithOutOfOrder(jinglePacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSessionInitiate(final JinglePacket jinglePacket) {
|
private void receiveSessionInitiate(final JinglePacket jinglePacket) {
|
||||||
if (isInitiator()) {
|
if (isInitiator()) {
|
||||||
Log.d(Config.LOGTAG, String.format("%s: received session-initiate even though we were initiating", id.account.getJid().asBareJid()));
|
Log.d(Config.LOGTAG, String.format("%s: received session-initiate even though we were initiating", id.account.getJid().asBareJid()));
|
||||||
//TODO respond with out-of-order
|
respondWithOutOfOrder(jinglePacket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final RtpContentMap contentMap;
|
final RtpContentMap contentMap;
|
||||||
|
@ -150,6 +184,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
contentMap = RtpContentMap.of(jinglePacket);
|
contentMap = RtpContentMap.of(jinglePacket);
|
||||||
contentMap.requireContentDescriptions();
|
contentMap.requireContentDescriptions();
|
||||||
} catch (IllegalArgumentException | IllegalStateException | NullPointerException e) {
|
} catch (IllegalArgumentException | IllegalStateException | NullPointerException e) {
|
||||||
|
respondOk(jinglePacket);
|
||||||
|
sendSessionTerminate(Reason.FAILED_APPLICATION);
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", e);
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -161,6 +197,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
target = State.SESSION_INITIALIZED;
|
target = State.SESSION_INITIALIZED;
|
||||||
}
|
}
|
||||||
if (transition(target)) {
|
if (transition(target)) {
|
||||||
|
respondOk(jinglePacket);
|
||||||
this.initiatorRtpContentMap = contentMap;
|
this.initiatorRtpContentMap = contentMap;
|
||||||
if (target == State.SESSION_INITIALIZED_PRE_APPROVED) {
|
if (target == State.SESSION_INITIALIZED_PRE_APPROVED) {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": automatically accepting session-initiate");
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": automatically accepting session-initiate");
|
||||||
|
@ -171,13 +208,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, String.format("%s: received session-initiate while in state %s", id.account.getJid().asBareJid(), state));
|
Log.d(Config.LOGTAG, String.format("%s: received session-initiate while in state %s", id.account.getJid().asBareJid(), state));
|
||||||
|
respondWithOutOfOrder(jinglePacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSessionAccept(final JinglePacket jinglePacket) {
|
private void receiveSessionAccept(final JinglePacket jinglePacket) {
|
||||||
if (!isInitiator()) {
|
if (!isInitiator()) {
|
||||||
Log.d(Config.LOGTAG, String.format("%s: received session-accept even though we were responding", id.account.getJid().asBareJid()));
|
Log.d(Config.LOGTAG, String.format("%s: received session-accept even though we were responding", id.account.getJid().asBareJid()));
|
||||||
//TODO respond with out-of-order
|
respondWithOutOfOrder(jinglePacket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final RtpContentMap contentMap;
|
final RtpContentMap contentMap;
|
||||||
|
@ -185,28 +223,43 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
contentMap = RtpContentMap.of(jinglePacket);
|
contentMap = RtpContentMap.of(jinglePacket);
|
||||||
contentMap.requireContentDescriptions();
|
contentMap.requireContentDescriptions();
|
||||||
} catch (IllegalArgumentException | IllegalStateException | NullPointerException e) {
|
} catch (IllegalArgumentException | IllegalStateException | NullPointerException e) {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", e);
|
respondOk(jinglePacket);
|
||||||
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents in session-accept", e);
|
||||||
|
webRTCWrapper.close();
|
||||||
|
sendSessionTerminate(Reason.FAILED_APPLICATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG, "processing session-accept with " + contentMap.contents.size() + " contents");
|
Log.d(Config.LOGTAG, "processing session-accept with " + contentMap.contents.size() + " contents");
|
||||||
if (transition(State.SESSION_ACCEPTED)) {
|
if (transition(State.SESSION_ACCEPTED)) {
|
||||||
|
respondOk(jinglePacket);
|
||||||
receiveSessionAccept(contentMap);
|
receiveSessionAccept(contentMap);
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, String.format("%s: received session-accept while in state %s", id.account.getJid().asBareJid(), state));
|
Log.d(Config.LOGTAG, String.format("%s: received session-accept while in state %s", id.account.getJid().asBareJid(), state));
|
||||||
//TODO out-of-order
|
respondOk(jinglePacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSessionAccept(final RtpContentMap contentMap) {
|
private void receiveSessionAccept(final RtpContentMap contentMap) {
|
||||||
this.responderRtpContentMap = contentMap;
|
this.responderRtpContentMap = contentMap;
|
||||||
|
final SessionDescription sessionDescription;
|
||||||
|
try {
|
||||||
|
sessionDescription = SessionDescription.of(contentMap);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable convert offer from session-accept to SDP", e);
|
||||||
|
webRTCWrapper.close();
|
||||||
|
sendSessionTerminate(Reason.FAILED_APPLICATION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
org.webrtc.SessionDescription answer = new org.webrtc.SessionDescription(
|
org.webrtc.SessionDescription answer = new org.webrtc.SessionDescription(
|
||||||
org.webrtc.SessionDescription.Type.ANSWER,
|
org.webrtc.SessionDescription.Type.ANSWER,
|
||||||
SessionDescription.of(contentMap).toString()
|
sessionDescription.toString()
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
this.webRTCWrapper.setRemoteDescription(answer).get();
|
this.webRTCWrapper.setRemoteDescription(answer).get();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Config.LOGTAG, "unable to receive session accept", e);
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to set remote description after receiving session-accept", e);
|
||||||
|
webRTCWrapper.close();
|
||||||
|
sendSessionTerminate(Reason.FAILED_APPLICATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,8 +272,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
try {
|
try {
|
||||||
offer = SessionDescription.of(rtpContentMap);
|
offer = SessionDescription.of(rtpContentMap);
|
||||||
} catch (final IllegalArgumentException e) {
|
} catch (final IllegalArgumentException e) {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to process offer", e);
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable convert offer from session-initiate to SDP", e);
|
||||||
//TODO terminate session with application error
|
webRTCWrapper.close();
|
||||||
|
sendSessionTerminate(Reason.FAILED_APPLICATION);
|
||||||
|
;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendSessionAccept(offer);
|
sendSessionAccept(offer);
|
||||||
|
@ -228,7 +283,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
|
|
||||||
private void sendSessionAccept(SessionDescription offer) {
|
private void sendSessionAccept(SessionDescription offer) {
|
||||||
discoverIceServers(iceServers -> {
|
discoverIceServers(iceServers -> {
|
||||||
setupWebRTC(iceServers);
|
try {
|
||||||
|
setupWebRTC(iceServers);
|
||||||
|
} catch (WebRTCWrapper.InitializationException e) {
|
||||||
|
sendSessionTerminate(Reason.FAILED_APPLICATION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(
|
final org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(
|
||||||
org.webrtc.SessionDescription.Type.OFFER,
|
org.webrtc.SessionDescription.Type.OFFER,
|
||||||
offer.toString()
|
offer.toString()
|
||||||
|
@ -351,7 +411,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
this.jingleConnectionManager.finishConnection(this);
|
this.jingleConnectionManager.finishConnection(this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO a carbon copied proceed from another client of mine has the same logic as `accept`
|
|
||||||
Log.d(Config.LOGTAG, String.format("%s: ignoring proceed from %s. was expected from %s", id.account.getJid().asBareJid(), from, id.with));
|
Log.d(Config.LOGTAG, String.format("%s: ignoring proceed from %s. was expected from %s", id.account.getJid().asBareJid(), from, id.with));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,7 +433,13 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
private void sendSessionInitiate(final State targetState) {
|
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");
|
||||||
discoverIceServers(iceServers -> {
|
discoverIceServers(iceServers -> {
|
||||||
setupWebRTC(iceServers);
|
try {
|
||||||
|
setupWebRTC(iceServers);
|
||||||
|
} catch (WebRTCWrapper.InitializationException e) {
|
||||||
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to initialize webrtc");
|
||||||
|
transitionOrThrow(State.TERMINATED_APPLICATION_FAILURE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get();
|
org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get();
|
||||||
final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
|
final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
|
||||||
|
@ -382,8 +447,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
|
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
|
||||||
sendSessionInitiate(rtpContentMap, targetState);
|
sendSessionInitiate(rtpContentMap, targetState);
|
||||||
this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
|
this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
Log.d(Config.LOGTAG, "unable to sendSessionInitiate", e);
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", e);
|
||||||
|
webRTCWrapper.close();
|
||||||
|
if (isInState(targetState)) {
|
||||||
|
sendSessionTerminate(Reason.FAILED_APPLICATION);
|
||||||
|
} else {
|
||||||
|
transitionOrThrow(State.TERMINATED_APPLICATION_FAILURE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -422,8 +493,43 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
|
|
||||||
private void send(final JinglePacket jinglePacket) {
|
private void send(final JinglePacket jinglePacket) {
|
||||||
jinglePacket.setTo(id.with);
|
jinglePacket.setTo(id.with);
|
||||||
//TODO track errors
|
xmppConnectionService.sendIqPacket(id.account, jinglePacket, (account, response) -> {
|
||||||
xmppConnectionService.sendIqPacket(id.account, jinglePacket, null);
|
if (response.getType() == IqPacket.TYPE.ERROR) {
|
||||||
|
final String errorCondition = response.getErrorCondition();
|
||||||
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received IQ-error from " + response.getFrom() + " in RTP session. " + errorCondition);
|
||||||
|
this.webRTCWrapper.close();
|
||||||
|
final State target;
|
||||||
|
if (Arrays.asList(
|
||||||
|
"service-unavailable",
|
||||||
|
"recipient-unavailable",
|
||||||
|
"remote-server-not-found",
|
||||||
|
"remote-server-timeout"
|
||||||
|
).contains(errorCondition)) {
|
||||||
|
target = State.TERMINATED_CONNECTIVITY_ERROR;
|
||||||
|
} else {
|
||||||
|
target = State.TERMINATED_APPLICATION_FAILURE;
|
||||||
|
}
|
||||||
|
if (transition(target)) {
|
||||||
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": terminated session with " + id.with);
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not transitioning because already at state=" + this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (response.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||||
|
this.webRTCWrapper.close();
|
||||||
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received IQ timeout in RTP session with " + id.with + ". terminating with connectivity error");
|
||||||
|
transition(State.TERMINATED_CONNECTIVITY_ERROR);
|
||||||
|
this.jingleConnectionManager.finishConnection(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void respondWithOutOfOrder(final JinglePacket jinglePacket) {
|
||||||
|
jingleConnectionManager.respondWithJingleError(id.account, jinglePacket, "out-of-order", "unexpected-request", "wait");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void respondOk(final JinglePacket jinglePacket) {
|
||||||
|
xmppConnectionService.sendIqPacket(id.account, jinglePacket.generateResponse(IqPacket.TYPE.RESULT), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RtpEndUserState getEndUserState() {
|
public RtpEndUserState getEndUserState() {
|
||||||
|
@ -474,6 +580,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
return RtpEndUserState.ENDED;
|
return RtpEndUserState.ENDED;
|
||||||
case TERMINATED_CONNECTIVITY_ERROR:
|
case TERMINATED_CONNECTIVITY_ERROR:
|
||||||
return RtpEndUserState.CONNECTIVITY_ERROR;
|
return RtpEndUserState.CONNECTIVITY_ERROR;
|
||||||
|
case TERMINATED_APPLICATION_FAILURE:
|
||||||
|
return RtpEndUserState.APPLICATION_ERROR;
|
||||||
}
|
}
|
||||||
throw new IllegalStateException(String.format("%s has no equivalent EndUserState", this.state));
|
throw new IllegalStateException(String.format("%s has no equivalent EndUserState", this.state));
|
||||||
}
|
}
|
||||||
|
@ -526,7 +634,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
throw new IllegalStateException("called 'endCall' while in state " + this.state);
|
throw new IllegalStateException("called 'endCall' while in state " + this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupWebRTC(final List<PeerConnection.IceServer> iceServers) {
|
private void setupWebRTC(final List<PeerConnection.IceServer> iceServers) throws WebRTCWrapper.InitializationException {
|
||||||
this.webRTCWrapper.setup(this.xmppConnectionService);
|
this.webRTCWrapper.setup(this.xmppConnectionService);
|
||||||
this.webRTCWrapper.initializePeerConnection(iceServers);
|
this.webRTCWrapper.initializePeerConnection(iceServers);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ public enum RtpEndUserState {
|
||||||
CONNECTED, //session-accepted and webrtc peer connection is connected
|
CONNECTED, //session-accepted and webrtc peer connection is connected
|
||||||
FINDING_DEVICE, //'propose' has been sent out; no 184 ack yet
|
FINDING_DEVICE, //'propose' has been sent out; no 184 ack yet
|
||||||
RINGING, //'propose' has been sent out and it has been 184 acked
|
RINGING, //'propose' has been sent out and it has been 184 acked
|
||||||
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
|
||||||
ENDED, //close UI
|
ENDED, //close UI
|
||||||
DECLINED_OR_BUSY, //other party declined; no retry button
|
DECLINED_OR_BUSY, //other party declined; no retry button
|
||||||
CONNECTIVITY_ERROR //network error; retry button
|
CONNECTIVITY_ERROR, //network error; retry button
|
||||||
|
APPLICATION_ERROR //something rather bad happened; libwebrtc failed or we got in IQ-error
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ public class WebRTCWrapper {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializePeerConnection(final List<PeerConnection.IceServer> iceServers) {
|
public void initializePeerConnection(final List<PeerConnection.IceServer> iceServers) throws InitializationException {
|
||||||
PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
|
PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
|
||||||
|
|
||||||
CameraVideoCapturer capturer = null;
|
CameraVideoCapturer capturer = null;
|
||||||
|
@ -195,7 +195,7 @@ public class WebRTCWrapper {
|
||||||
|
|
||||||
final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver);
|
final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver);
|
||||||
if (peerConnection == null) {
|
if (peerConnection == null) {
|
||||||
throw new IllegalStateException("Unable to create PeerConnection");
|
throw new InitializationException("Unable to create PeerConnection");
|
||||||
}
|
}
|
||||||
peerConnection.addStream(stream);
|
peerConnection.addStream(stream);
|
||||||
peerConnection.setAudioPlayout(true);
|
peerConnection.setAudioPlayout(true);
|
||||||
|
@ -344,6 +344,13 @@ public class WebRTCWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class InitializationException extends Exception {
|
||||||
|
|
||||||
|
private InitializationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface EventCallback {
|
public interface EventCallback {
|
||||||
void onIceCandidate(IceCandidate iceCandidate);
|
void onIceCandidate(IceCandidate iceCandidate);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import android.support.annotation.NonNull;
|
||||||
import com.google.common.base.CaseFormat;
|
import com.google.common.base.CaseFormat;
|
||||||
|
|
||||||
public enum Reason {
|
public enum Reason {
|
||||||
SUCCESS, DECLINE, BUSY, CANCEL, CONNECTIVITY_ERROR, FAILED_TRANSPORT, TIMEOUT, UNKNOWN;
|
SUCCESS, DECLINE, BUSY, CANCEL, CONNECTIVITY_ERROR, FAILED_TRANSPORT, FAILED_APPLICATION, TIMEOUT, UNKNOWN;
|
||||||
|
|
||||||
public static Reason of(final String value) {
|
public static Reason of(final String value) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -31,6 +31,18 @@ abstract public class AbstractAcknowledgeableStanza extends AbstractStanza {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getErrorCondition() {
|
||||||
|
Element error = findChild("error");
|
||||||
|
if (error != null) {
|
||||||
|
for(Element element : error.getChildren()) {
|
||||||
|
if (!element.getName().equals("text")) {
|
||||||
|
return element.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean valid() {
|
public boolean valid() {
|
||||||
return InvalidJid.isValid(getFrom()) && InvalidJid.isValid(getTo());
|
return InvalidJid.isValid(getFrom()) && InvalidJid.isValid(getTo());
|
||||||
}
|
}
|
||||||
|
|
|
@ -898,6 +898,7 @@
|
||||||
<string name="rtp_state_ringing">Ringing</string>
|
<string name="rtp_state_ringing">Ringing</string>
|
||||||
<string name="rtp_state_declined_or_busy">Busy</string>
|
<string name="rtp_state_declined_or_busy">Busy</string>
|
||||||
<string name="rtp_state_connectivity_error">Unable to connect call</string>
|
<string name="rtp_state_connectivity_error">Unable to connect call</string>
|
||||||
|
<string name="rtp_state_application_failure">Application failure</string>
|
||||||
<plurals name="view_users">
|
<plurals name="view_users">
|
||||||
<item quantity="one">View %1$d Participant</item>
|
<item quantity="one">View %1$d Participant</item>
|
||||||
<item quantity="other">View %1$d Participants</item>
|
<item quantity="other">View %1$d Participants</item>
|
||||||
|
|
Loading…
Reference in a new issue