make retract jingle messages work

This commit is contained in:
Daniel Gultsch 2020-04-08 09:42:06 +02:00
parent e2f1cec2e5
commit 7909a72d43
8 changed files with 187 additions and 40 deletions

View file

@ -244,4 +244,13 @@ public class MessageGenerator extends AbstractGenerator {
packet.addChild("request", "urn:xmpp:receipts"); packet.addChild("request", "urn:xmpp:receipts");
return packet; return packet;
} }
public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
final MessagePacket packet = new MessagePacket();
packet.setTo(proposal.with);
final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
propose.setAttribute("id", proposal.sessionId);
propose.addChild("description", Namespace.JINGLE_APPS_RTP);
return packet;
}
} }

View file

@ -351,7 +351,7 @@ public class NotificationService {
builder.addAction(new NotificationCompat.Action.Builder( builder.addAction(new NotificationCompat.Action.Builder(
R.drawable.ic_call_white_24dp, R.drawable.ic_call_white_24dp,
mXmppConnectionService.getString(R.string.answer_call), mXmppConnectionService.getString(R.string.answer_call),
createPendingRtpSession(id, RtpSessionActivity.ACTION_ACCEPT, 103)) createPendingRtpSession(id, RtpSessionActivity.ACTION_ACCEPT_CALL, 103))
.build()); .build());
final Notification notification = builder.build(); final Notification notification = builder.build();
notification.flags = notification.flags | Notification.FLAG_INSISTENT; notification.flags = notification.flags | Notification.FLAG_INSISTENT;

View file

@ -3977,9 +3977,9 @@ public class XmppConnectionService extends Service {
} }
} }
public void notifyJingleRtpConnectionUpdate(final Account account, final Jid with, final RtpEndUserState state) { public void notifyJingleRtpConnectionUpdate(final Account account, final Jid with, final String sessionId, final RtpEndUserState state) {
for(OnJingleRtpConnectionUpdate listener : threadSafeList(this.onJingleRtpConnectionUpdate)) { for(OnJingleRtpConnectionUpdate listener : threadSafeList(this.onJingleRtpConnectionUpdate)) {
listener.onJingleRtpConnectionUpdate(account, with, state); listener.onJingleRtpConnectionUpdate(account, with, sessionId, state);
} }
} }
@ -4661,7 +4661,7 @@ public class XmppConnectionService extends Service {
} }
public interface OnJingleRtpConnectionUpdate { public interface OnJingleRtpConnectionUpdate {
void onJingleRtpConnectionUpdate(final Account account, final Jid with, final RtpEndUserState state); void onJingleRtpConnectionUpdate(final Account account, final Jid with, final String sessionId, final RtpEndUserState state);
} }
public interface OnAccountUpdate { public interface OnAccountUpdate {

View file

@ -1243,7 +1243,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private void triggerRtpSession() { private void triggerRtpSession() {
final Contact contact = conversation.getContact(); final Contact contact = conversation.getContact();
activity.xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(conversation.getAccount(), contact); final Intent intent = new Intent(activity, RtpSessionActivity.class);
intent.setAction(RtpSessionActivity.ACTION_MAKE_VOICE_CALL);
intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, contact.getAccount().getJid().toEscapedString());
intent.putExtra(RtpSessionActivity.EXTRA_WITH, contact.getJid().asBareJid().toEscapedString());
startActivity(intent);
} }
private void handleAttachmentSelection(MenuItem item) { private void handleAttachmentSelection(MenuItem item) {

View file

@ -8,6 +8,7 @@ import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Arrays;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
@ -20,12 +21,16 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
import static java.util.Arrays.asList;
public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate { public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate {
public static final String EXTRA_WITH = "with"; public static final String EXTRA_WITH = "with";
public static final String EXTRA_SESSION_ID = "session_id"; public static final String EXTRA_SESSION_ID = "session_id";
public static final String ACTION_ACCEPT = "accept"; public static final String ACTION_ACCEPT_CALL = "action_accept_call";
public static final String ACTION_MAKE_VOICE_CALL = "action_make_voice_call";
public static final String ACTION_MAKE_VIDEO_CALL = "action_make_video_call";
private WeakReference<JingleRtpConnection> rtpConnectionReference; private WeakReference<JingleRtpConnection> rtpConnectionReference;
@ -53,7 +58,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
} }
private void endCall(View view) { private void endCall(View view) {
requireRtpConnection().endCall(); if (this.rtpConnectionReference == null) {
final Intent intent = getIntent();
final Account account = extractAccount(intent);
final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH));
xmppConnectionService.getJingleConnectionManager().retractSessionProposal(account, with.asBareJid());
finish();
} else {
requireRtpConnection().endCall();
}
} }
private void rejectCall(View view) { private void rejectCall(View view) {
@ -73,8 +86,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
@Override @Override
public void onNewIntent(final Intent intent) { public void onNewIntent(final Intent intent) {
super.onNewIntent(intent); super.onNewIntent(intent);
if (ACTION_ACCEPT.equals(intent.getAction())) { if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
Log.d(Config.LOGTAG,"accepting through onNewIntent()"); Log.d(Config.LOGTAG, "accepting through onNewIntent()");
requireRtpConnection().acceptCall(); requireRtpConnection().acceptCall();
} }
} }
@ -83,28 +96,50 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
void onBackendConnected() { void onBackendConnected() {
final Intent intent = getIntent(); final Intent intent = getIntent();
final Account account = extractAccount(intent); final Account account = extractAccount(intent);
final String with = intent.getStringExtra(EXTRA_WITH); final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH));
final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID); final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
if (with != null && sessionId != null) { if (sessionId != null) {
final WeakReference<JingleRtpConnection> reference = xmppConnectionService.getJingleConnectionManager() initializeActivityWithRunningRapSession(account, with, sessionId);
.findJingleRtpConnection(account, Jid.ofEscaped(with), sessionId); if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
if (reference == null || reference.get() == null) { Log.d(Config.LOGTAG, "intent action was accept");
finish();
return;
}
this.rtpConnectionReference = reference;
binding.with.setText(getWith().getDisplayName());
final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
final String action = intent.getAction();
updateStateDisplay(currentState);
updateButtonConfiguration(currentState);
if (ACTION_ACCEPT.equals(action)) {
Log.d(Config.LOGTAG,"intent action was accept");
requireRtpConnection().acceptCall(); requireRtpConnection().acceptCall();
} }
} else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(intent.getAction())) {
xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with);
binding.with.setText(account.getRoster().getContact(with).getDisplayName());
} }
} }
private void initializeActivityWithRunningRapSession(final Account account, Jid with, String sessionId) {
final WeakReference<JingleRtpConnection> reference = xmppConnectionService.getJingleConnectionManager()
.findJingleRtpConnection(account, with, sessionId);
if (reference == null || reference.get() == null) {
finish();
return;
}
this.rtpConnectionReference = reference;
final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
if (currentState == RtpEndUserState.ENDED) {
finish();
return;
}
binding.with.setText(getWith().getDisplayName());
updateStateDisplay(currentState);
updateButtonConfiguration(currentState);
}
private void reInitializeActivityWithRunningRapSession(final Account account, Jid with, String sessionId) {
runOnUiThread(() -> {
initializeActivityWithRunningRapSession(account, with, sessionId);
});
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
intent.putExtra(EXTRA_WITH, with.toEscapedString());
intent.putExtra(EXTRA_SESSION_ID, sessionId);
setIntent(intent);
}
private void updateStateDisplay(final RtpEndUserState state) { private void updateStateDisplay(final RtpEndUserState state) {
switch (state) { switch (state) {
case INCOMING_CALL: case INCOMING_CALL:
@ -122,6 +157,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
case ENDING_CALL: case ENDING_CALL:
binding.status.setText(R.string.rtp_state_ending_call); binding.status.setText(R.string.rtp_state_ending_call);
break; break;
case FINDING_DEVICE:
binding.status.setText(R.string.rtp_state_finding_device);
break;
case RINGING:
binding.status.setText(R.string.rtp_state_ringing);
} }
} }
@ -156,9 +196,19 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
} }
@Override @Override
public void onJingleRtpConnectionUpdate(Account account, Jid with, RtpEndUserState state) { public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) {
Log.d(Config.LOGTAG,"onJingleRtpConnectionUpdate("+state+")");
if (with.isBareJid()) {
updateRtpSessionProposalState(with, state);
return;
}
if (this.rtpConnectionReference == null) {
//this happens when going from proposed session to actual session
reInitializeActivityWithRunningRapSession(account, with, sessionId);
return;
}
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) && id.sessionId.equals(sessionId)) {
if (state == RtpEndUserState.ENDED) { if (state == RtpEndUserState.ENDED) {
finish(); finish();
return; return;
@ -170,6 +220,19 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
} else { } else {
Log.d(Config.LOGTAG, "received update for other rtp session"); Log.d(Config.LOGTAG, "received update for other rtp session");
} }
}
private void updateRtpSessionProposalState(Jid with, RtpEndUserState state) {
final Intent intent = getIntent();
final String intentExtraWith = intent == null ? null : intent.getStringExtra(EXTRA_WITH);
if (intentExtraWith == null) {
return;
}
if (Jid.ofEscaped(intentExtraWith).asBareJid().equals(with)) {
runOnUiThread(() -> {
updateStateDisplay(state);
updateButtonConfiguration(state);
});
}
} }
} }

View file

@ -188,12 +188,52 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
} }
public void proposeJingleRtpSession(final Account account, final Contact contact) { public void retractSessionProposal(final Account account, final Jid with) {
final RtpSessionProposal proposal = RtpSessionProposal.of(account, contact.getJid().asBareJid());
synchronized (this.rtpSessionProposals) { synchronized (this.rtpSessionProposals) {
RtpSessionProposal matchingProposal = null;
for (RtpSessionProposal proposal : this.rtpSessionProposals.keySet()) {
if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
matchingProposal = proposal;
break;
}
}
if (matchingProposal != null) {
this.rtpSessionProposals.remove(matchingProposal);
final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(matchingProposal);
Log.d(Config.LOGTAG, messagePacket.toString());
mXmppConnectionService.sendMessagePacket(account, messagePacket);
}
}
}
public void proposeJingleRtpSession(final Account account, final Jid with) {
synchronized (this.rtpSessionProposals) {
for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
RtpSessionProposal proposal = entry.getKey();
if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
final DeviceDiscoveryState preexistingState = entry.getValue();
if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) {
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account,
with,
proposal.sessionId,
preexistingState.toEndUserState()
);
return;
}
}
}
final RtpSessionProposal proposal = RtpSessionProposal.of(account, with.asBareJid());
this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING); this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account,
proposal.with,
proposal.sessionId,
RtpEndUserState.FINDING_DEVICE
);
final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal); final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
Log.d(Config.LOGTAG,messagePacket.toString()); Log.d(Config.LOGTAG, messagePacket.toString());
mXmppConnectionService.sendMessagePacket(account, messagePacket); mXmppConnectionService.sendMessagePacket(account, messagePacket);
} }
} }
@ -255,24 +295,25 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
public void updateProposedSessionDiscovered(Account account, Jid from, String sessionId, final DeviceDiscoveryState target) { public void updateProposedSessionDiscovered(Account account, Jid from, String sessionId, final DeviceDiscoveryState target) {
final RtpSessionProposal sessionProposal = new RtpSessionProposal(account,from.asBareJid(),sessionId); final RtpSessionProposal sessionProposal = new RtpSessionProposal(account, from.asBareJid(), sessionId);
synchronized (this.rtpSessionProposals) { synchronized (this.rtpSessionProposals) {
final DeviceDiscoveryState currentState = rtpSessionProposals.get(sessionProposal); final DeviceDiscoveryState currentState = rtpSessionProposals.get(sessionProposal);
if (currentState == null) { if (currentState == null) {
Log.d(Config.LOGTAG,"unable to find session proposal for session id "+sessionId); Log.d(Config.LOGTAG, "unable to find session proposal for session id " + sessionId);
return; return;
} }
if (currentState == DeviceDiscoveryState.DISCOVERED) { if (currentState == DeviceDiscoveryState.DISCOVERED) {
Log.d(Config.LOGTAG,"session proposal already at discovered. not going to fall back"); Log.d(Config.LOGTAG, "session proposal already at discovered. not going to fall back");
return; return;
} }
this.rtpSessionProposals.put(sessionProposal, target); this.rtpSessionProposals.put(sessionProposal, target);
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": flagging session "+sessionId+" as "+target); mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, target.toEndUserState());
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": flagging session " + sessionId + " as " + target);
} }
} }
public void rejectRtpSession(final String sessionId) { public void rejectRtpSession(final String sessionId) {
for(final AbstractJingleConnection connection : this.connections.values()) { for (final AbstractJingleConnection connection : this.connections.values()) {
if (connection.getId().sessionId.equals(sessionId)) { if (connection.getId().sessionId.equals(sessionId)) {
if (connection instanceof JingleRtpConnection) { if (connection instanceof JingleRtpConnection) {
((JingleRtpConnection) connection).rejectCall(); ((JingleRtpConnection) connection).rejectCall();
@ -313,6 +354,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
public enum DeviceDiscoveryState { public enum DeviceDiscoveryState {
SEARCHING, DISCOVERED, FAILED SEARCHING, DISCOVERED, FAILED;
public RtpEndUserState toEndUserState() {
switch (this) {
case SEARCHING:
return RtpEndUserState.FINDING_DEVICE;
case DISCOVERED:
return RtpEndUserState.RINGING;
default:
return RtpEndUserState.CONNECTIVITY_ERROR;
}
}
} }
} }

View file

@ -1,6 +1,5 @@
package eu.siacs.conversations.xmpp.jingle; package eu.siacs.conversations.xmpp.jingle;
import android.content.Intent;
import android.util.Log; import android.util.Log;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -17,7 +16,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.ui.RtpSessionActivity;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
@ -34,7 +32,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, State.REJECTED)); 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));
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();
@ -234,6 +232,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
break; break;
case "proceed": case "proceed":
receiveProceed(from, message); receiveProceed(from, message);
break;
case "retract":
receiveRetract(from, message);
break;
default: default:
break; break;
} }
@ -272,6 +274,21 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
} }
private void receiveRetract(final Jid from, final Element retract) {
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");
//TODO create missed call notification/message
jingleConnectionManager.finishConnection(this);
} else {
Log.d(Config.LOGTAG, "ignoring retract because already in " + this.state);
}
} else {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received retract from " + from + ". expected retract from" + id.with + ". ignoring");
}
}
private void sendSessionInitiate() { private void sendSessionInitiate() {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate"); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate");
setupWebRTC(); setupWebRTC();
@ -472,6 +489,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
private void updateEndUserState() { private void updateEndUserState() {
xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, getEndUserState()); xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, id.sessionId, getEndUserState());
} }
} }

View file

@ -894,6 +894,8 @@
<string name="rtp_state_ending_call">Ending call</string> <string name="rtp_state_ending_call">Ending call</string>
<string name="answer_call">Answer</string> <string name="answer_call">Answer</string>
<string name="dismiss_call">Dismiss</string> <string name="dismiss_call">Dismiss</string>
<string name="rtp_state_finding_device">Locating devices</string>
<string name="rtp_state_ringing">Ringing</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>