add optional strict offline checking for calls

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/Config.java                                    |  2 
src/main/java/eu/siacs/conversations/entities/Presences.java                        |  6 
src/main/java/eu/siacs/conversations/services/CallIntegration.java                  |  1 
src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java | 28 
src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java                     |  5 
src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java               | 35 
src/main/res/values/strings.xml                                                     |  1 
7 files changed, 52 insertions(+), 26 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/Config.java 🔗

@@ -121,6 +121,8 @@ public final class Config {
             false; // disables STUN/TURN and Proxy65 look up (useful to debug IBB fallback)
     public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true;
     public static final boolean USE_JINGLE_MESSAGE_INIT = true;
+
+    public static final boolean JINGLE_MESSAGE_INIT_STRICT_OFFLINE_CHECK = false;
     public static final boolean DISABLE_HTTP_UPLOAD = false;
     public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts
     public static final boolean BACKGROUND_STANZA_LOGGING =

src/main/java/eu/siacs/conversations/entities/Presences.java 🔗

@@ -83,6 +83,12 @@ public class Presences {
         }
     }
 
+    public boolean isEmpty() {
+        synchronized (this.presences) {
+            return this.presences.isEmpty();
+        }
+    }
+
     public String[] toResourceArray() {
         synchronized (this.presences) {
             final String[] presencesArray = new String[presences.size()];

src/main/java/eu/siacs/conversations/services/CallIntegration.java 🔗

@@ -303,6 +303,7 @@ public class CallIntegration extends Connection {
     @Override
     public void onStateChanged(final int state) {
         Log.d(Config.LOGTAG, "onStateChanged(" + state + ")");
+        // TODO devices before selfManaged() will likely have to play their own ringback sound
         if (state == STATE_ACTIVE) {
             playConnectedSound();
         } else if (state == STATE_DISCONNECTED) {

src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java 🔗

@@ -97,15 +97,26 @@ public class CallIntegrationConnectionService extends ConnectionService {
         intent.putExtra(RtpSessionActivity.EXTRA_WITH, with.toEscapedString());
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        final CallIntegration callIntegration;
+        final Connection callIntegration;
         if (with.isBareJid()) {
-            final var proposal =
-                    service.getJingleConnectionManager()
-                            .proposeJingleRtpSession(account, with, media);
-
-            intent.putExtra(
-                    RtpSessionActivity.EXTRA_LAST_REPORTED_STATE,
-                    RtpEndUserState.FINDING_DEVICE.toString());
+            final var contact = account.getRoster().getContact(with);
+            if (Config.JINGLE_MESSAGE_INIT_STRICT_OFFLINE_CHECK
+                    && contact.getPresences().isEmpty()) {
+                intent.putExtra(
+                        RtpSessionActivity.EXTRA_LAST_REPORTED_STATE,
+                        RtpEndUserState.CONTACT_OFFLINE.toString());
+                callIntegration =
+                        Connection.createFailedConnection(
+                                new DisconnectCause(DisconnectCause.ERROR, "contact is offline"));
+            } else {
+                final var proposal =
+                        service.getJingleConnectionManager()
+                                .proposeJingleRtpSession(account, with, media);
+                intent.putExtra(
+                        RtpSessionActivity.EXTRA_LAST_REPORTED_STATE,
+                        RtpEndUserState.FINDING_DEVICE.toString());
+                callIntegration = proposal.getCallIntegration();
+            }
             if (Media.audioOnly(media)) {
                 intent.putExtra(
                         RtpSessionActivity.EXTRA_LAST_ACTION,
@@ -115,7 +126,6 @@ public class CallIntegrationConnectionService extends ConnectionService {
                         RtpSessionActivity.EXTRA_LAST_ACTION,
                         RtpSessionActivity.ACTION_MAKE_VIDEO_CALL);
             }
-            callIntegration = proposal.getCallIntegration();
         } else {
             final JingleRtpConnection jingleRtpConnection =
                     service.getJingleConnectionManager().initializeRtpSession(account, with, media);

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java 🔗

@@ -95,6 +95,7 @@ public class RtpSessionActivity extends XmppActivity
                     RtpEndUserState.APPLICATION_ERROR,
                     RtpEndUserState.SECURITY_ERROR,
                     RtpEndUserState.DECLINED_OR_BUSY,
+                    RtpEndUserState.CONTACT_OFFLINE,
                     RtpEndUserState.CONNECTIVITY_ERROR,
                     RtpEndUserState.CONNECTIVITY_LOST_ERROR,
                     RtpEndUserState.RETRACTED);
@@ -881,6 +882,7 @@ public class RtpSessionActivity extends XmppActivity
             case FINDING_DEVICE -> setTitle(R.string.rtp_state_finding_device);
             case RINGING -> setTitle(R.string.rtp_state_ringing);
             case DECLINED_OR_BUSY -> setTitle(R.string.rtp_state_declined_or_busy);
+            case CONTACT_OFFLINE -> setTitle(R.string.rtp_state_contact_offline);
             case CONNECTIVITY_ERROR -> setTitle(R.string.rtp_state_connectivity_error);
             case CONNECTIVITY_LOST_ERROR -> setTitle(R.string.rtp_state_connectivity_lost_error);
             case RETRACTED -> setTitle(R.string.rtp_state_retracted);
@@ -974,7 +976,8 @@ public class RtpSessionActivity extends XmppActivity
             this.binding.acceptCall.setOnClickListener((v -> acceptContentAdd(contentAddition)));
             this.binding.acceptCall.setImageResource(R.drawable.ic_baseline_check_24);
             this.binding.acceptCall.setVisibility(View.VISIBLE);
-        } else if (state == RtpEndUserState.DECLINED_OR_BUSY) {
+        } else if (asList(RtpEndUserState.DECLINED_OR_BUSY, RtpEndUserState.CONTACT_OFFLINE)
+                .contains(state)) {
             this.binding.rejectCall.setContentDescription(getString(R.string.exit));
             this.binding.rejectCall.setOnClickListener(this::exit);
             this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);

src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java 🔗

@@ -1,20 +1,23 @@
 package eu.siacs.conversations.xmpp.jingle;
 
 public enum RtpEndUserState {
-    INCOMING_CALL, //received a 'propose' message
-    CONNECTING, //session-initiate or session-accepted but no webrtc peer connection yet
-    CONNECTED, //session-accepted and webrtc peer connection is connected
-    RECONNECTING, //session-accepted and webrtc peer connection was connected once but is currently disconnected or failed
-    INCOMING_CONTENT_ADD, //session-accepted with a pending, incoming content-add
-    FINDING_DEVICE, //'propose' has been sent out; no 184 ack yet
-    RINGING, //'propose' has been sent out and it has been 184 acked
-    ACCEPTING_CALL, //'proceed' message has been sent; but no session-initiate has been received
-    ENDING_CALL, //libwebrt says 'closed' but session-terminate has not gone through
-    ENDED, //close UI
-    DECLINED_OR_BUSY, //other party declined; no retry button
-    CONNECTIVITY_ERROR, //network error; retry button
-    CONNECTIVITY_LOST_ERROR, //network error but for call duration > 0
-    RETRACTED, //user pressed home or power button during 'ringing' - shows retry button
-    APPLICATION_ERROR, //something rather bad happened; libwebrtc failed or we got in IQ-error
-    SECURITY_ERROR //problem with DTLS (missing) or verification
+    INCOMING_CALL, // received a 'propose' message
+    CONNECTING, // session-initiate or session-accepted but no webrtc peer connection yet
+    CONNECTED, // session-accepted and webrtc peer connection is connected
+    RECONNECTING, // session-accepted and webrtc peer connection was connected once but is currently
+                  // disconnected or failed
+    INCOMING_CONTENT_ADD, // session-accepted with a pending, incoming content-add
+    FINDING_DEVICE, // 'propose' has been sent out; no 184 ack yet
+    RINGING, // 'propose' has been sent out and it has been 184 acked
+    ACCEPTING_CALL, // 'proceed' message has been sent; but no session-initiate has been received
+    ENDING_CALL, // libwebrt says 'closed' but session-terminate has not gone through
+    ENDED, // close UI
+    DECLINED_OR_BUSY, // other party declined; no retry button
+    CONTACT_OFFLINE, // when `JINGLE_MESSAGE_INIT_STRICT_OFFLINE_CHECK` is true this shows up when
+                     // the contact is offline, generally similar to BUSY
+    CONNECTIVITY_ERROR, // network error; retry button
+    CONNECTIVITY_LOST_ERROR, // network error but for call duration > 0
+    RETRACTED, // user pressed home or power button during 'ringing' - shows retry button
+    APPLICATION_ERROR, // something rather bad happened; libwebrtc failed or we got in IQ-error
+    SECURITY_ERROR // problem with DTLS (missing) or verification
 }

src/main/res/values/strings.xml 🔗

@@ -929,6 +929,7 @@
     <string name="rtp_state_finding_device">Discovering devices</string>
     <string name="rtp_state_ringing">Ringing</string>
     <string name="rtp_state_declined_or_busy">Busy</string>
+    <string name="rtp_state_contact_offline">Contact is not available</string>
     <string name="rtp_state_connectivity_error">Could not connect call</string>
     <string name="rtp_state_connectivity_lost_error">Connection lost</string>
     <string name="rtp_state_retracted">Retracted call</string>