diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 393cec5d4..c0acf50b7 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -244,4 +244,13 @@ public class MessageGenerator extends AbstractGenerator { packet.addChild("request", "urn:xmpp:receipts"); 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; + } } diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 9a51393a9..da7028f35 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -351,7 +351,7 @@ public class NotificationService { builder.addAction(new NotificationCompat.Action.Builder( R.drawable.ic_call_white_24dp, mXmppConnectionService.getString(R.string.answer_call), - createPendingRtpSession(id, RtpSessionActivity.ACTION_ACCEPT, 103)) + createPendingRtpSession(id, RtpSessionActivity.ACTION_ACCEPT_CALL, 103)) .build()); final Notification notification = builder.build(); notification.flags = notification.flags | Notification.FLAG_INSISTENT; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 22df52035..14e94951f 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -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)) { - listener.onJingleRtpConnectionUpdate(account, with, state); + listener.onJingleRtpConnectionUpdate(account, with, sessionId, state); } } @@ -4661,7 +4661,7 @@ public class XmppConnectionService extends Service { } 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 { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index bb76d7256..ba531cc45 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1243,7 +1243,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private void triggerRtpSession() { 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) { diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 5778a3de9..b70dab72f 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -8,6 +8,7 @@ import android.view.View; import android.view.WindowManager; import java.lang.ref.WeakReference; +import java.util.Arrays; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -20,12 +21,16 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; import rocks.xmpp.addr.Jid; +import static java.util.Arrays.asList; + public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate { public static final String EXTRA_WITH = "with"; 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 rtpConnectionReference; @@ -53,7 +58,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } 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) { @@ -73,8 +86,8 @@ 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()"); + if (ACTION_ACCEPT_CALL.equals(intent.getAction())) { + Log.d(Config.LOGTAG, "accepting through onNewIntent()"); requireRtpConnection().acceptCall(); } } @@ -83,28 +96,50 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe void onBackendConnected() { final Intent intent = getIntent(); 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); - if (with != null && sessionId != null) { - final WeakReference reference = xmppConnectionService.getJingleConnectionManager() - .findJingleRtpConnection(account, Jid.ofEscaped(with), sessionId); - if (reference == null || reference.get() == null) { - 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"); + if (sessionId != null) { + initializeActivityWithRunningRapSession(account, with, sessionId); + if (ACTION_ACCEPT_CALL.equals(intent.getAction())) { + Log.d(Config.LOGTAG, "intent action was accept"); 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 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) { switch (state) { case INCOMING_CALL: @@ -122,6 +157,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe case ENDING_CALL: binding.status.setText(R.string.rtp_state_ending_call); 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 - 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(); - if (account == id.account && id.with.equals(with)) { + if (account == id.account && id.with.equals(with) && id.sessionId.equals(sessionId)) { if (state == RtpEndUserState.ENDED) { finish(); return; @@ -170,6 +220,19 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } else { 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); + }); + } } } 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 404cb0cc4..2c306816f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -188,12 +188,52 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } - public void proposeJingleRtpSession(final Account account, final Contact contact) { - final RtpSessionProposal proposal = RtpSessionProposal.of(account, contact.getJid().asBareJid()); + public void retractSessionProposal(final Account account, final Jid with) { 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 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); + mXmppConnectionService.notifyJingleRtpConnectionUpdate( + account, + proposal.with, + proposal.sessionId, + RtpEndUserState.FINDING_DEVICE + ); final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal); - Log.d(Config.LOGTAG,messagePacket.toString()); + Log.d(Config.LOGTAG, messagePacket.toString()); 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) { - final RtpSessionProposal sessionProposal = new RtpSessionProposal(account,from.asBareJid(),sessionId); + final RtpSessionProposal sessionProposal = new RtpSessionProposal(account, from.asBareJid(), sessionId); synchronized (this.rtpSessionProposals) { final DeviceDiscoveryState currentState = rtpSessionProposals.get(sessionProposal); 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; } 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; } 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) { - for(final AbstractJingleConnection connection : this.connections.values()) { + for (final AbstractJingleConnection connection : this.connections.values()) { if (connection.getId().sessionId.equals(sessionId)) { if (connection instanceof JingleRtpConnection) { ((JingleRtpConnection) connection).rejectCall(); @@ -313,6 +354,17 @@ public class JingleConnectionManager extends AbstractConnectionManager { } 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; + } + } } } 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 9b73b131d..28e06ed3a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.xmpp.jingle; -import android.content.Intent; import android.util.Log; import com.google.common.collect.ImmutableList; @@ -17,7 +16,6 @@ import java.util.List; import java.util.Map; import eu.siacs.conversations.Config; -import eu.siacs.conversations.ui.RtpSessionActivity; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; @@ -34,7 +32,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, 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.SESSION_INITIALIZED, ImmutableList.of(State.SESSION_ACCEPTED)); VALID_TRANSITIONS = transitionBuilder.build(); @@ -234,6 +232,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web break; case "proceed": receiveProceed(from, message); + break; + case "retract": + receiveRetract(from, message); + break; default: 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() { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate"); setupWebRTC(); @@ -472,6 +489,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void updateEndUserState() { - xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, getEndUserState()); + xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, id.sessionId, getEndUserState()); } } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index e7d20d98f..47e635368 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -894,6 +894,8 @@ Ending call Answer Dismiss + Locating devices + Ringing View %1$d Participant View %1$d Participants