show speaker selection during 'ringing'

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java                | 89 
src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java |  8 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java  | 32 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java      | 19 
src/main/java/eu/siacs/conversations/xmpp/jingle/OngoingRtpSession.java        |  9 
5 files changed, 127 insertions(+), 30 deletions(-)

Detailed changes

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

@@ -63,6 +63,7 @@ import eu.siacs.conversations.xmpp.jingle.ContentAddition;
 import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
 import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
 import eu.siacs.conversations.xmpp.jingle.Media;
+import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
 import eu.siacs.conversations.xmpp.jingle.RtpCapability;
 import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
 
@@ -117,6 +118,13 @@ public class RtpSessionActivity extends XmppActivity
                     RtpEndUserState.ACCEPTING_CALL,
                     RtpEndUserState.CONNECTING,
                     RtpEndUserState.RECONNECTING);
+    private static final List<RtpEndUserState> STATES_SHOWING_SPEAKER_CONFIGURATION =
+            new ImmutableList.Builder<RtpEndUserState>()
+                    .add(RtpEndUserState.FINDING_DEVICE)
+                    .add(RtpEndUserState.RINGING)
+                    .add(RtpEndUserState.CONNECTING)
+                    .addAll(STATES_CONSIDERED_CONNECTED)
+                    .build();
     private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
     private static final int REQUEST_ACCEPT_CALL = 0x1111;
     private static final int REQUEST_ACCEPT_CONTENT = 0x1112;
@@ -139,8 +147,13 @@ public class RtpSessionActivity extends XmppActivity
     public static Set<Media> actionToMedia(final String action) {
         if (ACTION_MAKE_VIDEO_CALL.equals(action)) {
             return ImmutableSet.of(Media.AUDIO, Media.VIDEO);
-        } else {
+        } else if (ACTION_MAKE_VOICE_CALL.equals(action)) {
             return ImmutableSet.of(Media.AUDIO);
+        } else {
+            Log.w(
+                    Config.LOGTAG,
+                    "actionToMedia can not get media set from unknown action " + action);
+            return Collections.emptySet();
         }
     }
 
@@ -274,14 +287,15 @@ public class RtpSessionActivity extends XmppActivity
     private void retractSessionProposal() {
         final Intent intent = getIntent();
         final String action = intent.getAction();
+        final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION);
         final Account account = extractAccount(intent);
         final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
         final String state = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
         if (!Intent.ACTION_VIEW.equals(action)
                 || state == null
                 || !END_CARD.contains(RtpEndUserState.valueOf(state))) {
-            resetIntent(
-                    account, with, RtpEndUserState.RETRACTED, actionToMedia(intent.getAction()));
+            final Set<Media> media = actionToMedia(lastAction == null ? action : lastAction);
+            resetIntent(account, with, RtpEndUserState.RETRACTED, media);
         }
         xmppConnectionService
                 .getJingleConnectionManager()
@@ -1049,6 +1063,14 @@ public class RtpSessionActivity extends XmppActivity
             } else {
                 this.binding.inCallActionLeft.setVisibility(View.GONE);
             }
+        } else if (STATES_SHOWING_SPEAKER_CONFIGURATION.contains(state)
+                && !isPictureInPicture()
+                && Media.audioOnly(media)) {
+            final CallIntegration callIntegration = requireCallIntegration();
+            updateInCallButtonConfigurationSpeaker(
+                    callIntegration.getSelectedAudioDevice(),
+                    callIntegration.getAudioDevices().size());
+            this.binding.inCallActionFarRight.setVisibility(View.GONE);
         } else {
             this.binding.inCallActionLeft.setVisibility(View.GONE);
             this.binding.inCallActionRight.setVisibility(View.GONE);
@@ -1297,17 +1319,13 @@ public class RtpSessionActivity extends XmppActivity
         }
     }
 
-    private void switchToEarpiece(View view) {
-        requireRtpConnection()
-                .getCallIntegration()
-                .setAudioDevice(CallIntegration.AudioDevice.EARPIECE);
+    private void switchToEarpiece(final View view) {
+        requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE);
         acquireProximityWakeLock();
     }
 
-    private void switchToSpeaker(View view) {
-        requireRtpConnection()
-                .getCallIntegration()
-                .setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE);
+    private void switchToSpeaker(final View view) {
+        requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE);
         releaseProximityWakeLock();
     }
 
@@ -1359,6 +1377,33 @@ public class RtpSessionActivity extends XmppActivity
         return connection;
     }
 
+    private CallIntegration requireCallIntegration() {
+        return requireOngoingRtpSession().getCallIntegration();
+    }
+
+    private OngoingRtpSession requireOngoingRtpSession() {
+        final JingleRtpConnection connection =
+                this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+        if (connection != null) {
+            return connection;
+        }
+        final Intent currentIntent = getIntent();
+        final String withExtra =
+                currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH);
+        final var account = extractAccount(currentIntent);
+        if (withExtra == null) {
+            throw new IllegalStateException("Current intent has no EXTRA_WITH");
+        }
+        final var matching =
+                xmppConnectionService
+                        .getJingleConnectionManager()
+                        .matchingProposal(account, Jid.of(withExtra));
+        if (matching.isPresent()) {
+            return matching.get();
+        }
+        throw new IllegalStateException("No matching session proposal");
+    }
+
     @Override
     public void onJingleRtpConnectionUpdate(
             Account account, Jid with, final String sessionId, RtpEndUserState state) {
@@ -1425,16 +1470,23 @@ public class RtpSessionActivity extends XmppActivity
                         + ", available:"
                         + availableAudioDevices);
         try {
-            final RtpEndUserState endUserState = requireRtpConnection().getEndUserState();
-            final Set<Media> media = getMedia();
+            final OngoingRtpSession ongoingRtpSession = requireOngoingRtpSession();
+            final RtpEndUserState endUserState;
+            if (ongoingRtpSession instanceof JingleRtpConnection jingleRtpConnection) {
+                endUserState = jingleRtpConnection.getEndUserState();
+            } else {
+                // for session proposals all end user states are functionally the same
+                endUserState = RtpEndUserState.RINGING;
+            }
+            final Set<Media> media = ongoingRtpSession.getMedia();
             if (END_CARD.contains(endUserState)) {
                 Log.d(
                         Config.LOGTAG,
                         "onAudioDeviceChanged() nothing to do because end card has been reached");
             } else {
-                if (Media.audioOnly(media) && endUserState == RtpEndUserState.CONNECTED) {
-                    final CallIntegration callIntegration =
-                            requireRtpConnection().getCallIntegration();
+                if (Media.audioOnly(media)
+                        && STATES_SHOWING_SPEAKER_CONFIGURATION.contains(endUserState)) {
+                    final CallIntegration callIntegration = requireCallIntegration();
                     updateInCallButtonConfigurationSpeaker(
                             callIntegration.getSelectedAudioDevice(),
                             callIntegration.getAudioDevices().size());
@@ -1457,16 +1509,17 @@ public class RtpSessionActivity extends XmppActivity
         if (withExtra == null) {
             return;
         }
+        final Set<Media> media = actionToMedia(currentIntent.getStringExtra(EXTRA_LAST_ACTION));
         if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) {
             runOnUiThread(
                     () -> {
                         updateVerifiedShield(false);
                         updateStateDisplay(state);
-                        updateButtonConfiguration(state);
+                        updateButtonConfiguration(state, media, null);
                         updateIncomingCallScreen(state);
                         invalidateOptionsMenu();
                     });
-            resetIntent(account, with, state, actionToMedia(currentIntent.getAction()));
+            resetIntent(account, with, state, media);
         }
     }
 

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

@@ -219,8 +219,7 @@ public abstract class AbstractJingleConnection {
         if (isTerminated()) {
             this.jingleConnectionManager.finishConnectionOrThrow(this);
         } else {
-            throw new AssertionError(
-                    String.format("Unable to call finish from %s", this.state));
+            throw new AssertionError(String.format("Unable to call finish from %s", this.state));
         }
     }
 
@@ -348,7 +347,7 @@ public abstract class AbstractJingleConnection {
         return features != null && features.contains(feature);
     }
 
-    public static class Id implements OngoingRtpSession {
+    public static class Id {
         public final Account account;
         public final Jid with;
         public final String sessionId;
@@ -400,17 +399,14 @@ public abstract class AbstractJingleConnection {
             return Objects.hashCode(account.getUuid(), with, sessionId);
         }
 
-        @Override
         public Account getAccount() {
             return account;
         }
 
-        @Override
         public Jid getWith() {
             return with;
         }
 
-        @Override
         public String getSessionId() {
             return sessionId;
         }

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

@@ -601,11 +601,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     public Optional<OngoingRtpSession> getOngoingRtpConnection(final Contact contact) {
         for (final Map.Entry<AbstractJingleConnection.Id, AbstractJingleConnection> entry :
                 this.connections.entrySet()) {
-            if (entry.getValue() instanceof JingleRtpConnection) {
+            if (entry.getValue() instanceof JingleRtpConnection jingleRtpConnection) {
                 final AbstractJingleConnection.Id id = entry.getKey();
                 if (id.account == contact.getAccount()
                         && id.with.asBareJid().equals(contact.getJid().asBareJid())) {
-                    return Optional.of(id);
+                    return Optional.of(jingleRtpConnection);
                 }
             }
         }
@@ -765,9 +765,22 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         mXmppConnectionService.sendMessagePacket(account, messagePacket);
     }
 
+    public Optional<RtpSessionProposal> matchingProposal(final Account account, final Jid with) {
+        synchronized (this.rtpSessionProposals) {
+            for (final Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+                    this.rtpSessionProposals.entrySet()) {
+                final RtpSessionProposal proposal = entry.getKey();
+                if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
+                    return Optional.of(proposal);
+                }
+            }
+        }
+        return Optional.absent();
+    }
+
     public boolean hasMatchingProposal(final Account account, final Jid with) {
         synchronized (this.rtpSessionProposals) {
-            for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+            for (final Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
                     this.rtpSessionProposals.entrySet()) {
                 final var state = entry.getValue();
                 final RtpSessionProposal proposal = entry.getKey();
@@ -1102,9 +1115,15 @@ public class JingleConnectionManager extends AbstractConnectionManager {
             return sessionId;
         }
 
+        @Override
         public CallIntegration getCallIntegration() {
             return this.callIntegration;
         }
+
+        @Override
+        public Set<Media> getMedia() {
+            return this.media;
+        }
     }
 
     public class ProposalStateCallback implements CallIntegration.Callback {
@@ -1126,8 +1145,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
 
         @Override
         public void onAudioDeviceChanged(
-                CallIntegration.AudioDevice selectedAudioDevice,
-                Set<CallIntegration.AudioDevice> availableAudioDevices) {}
+                final CallIntegration.AudioDevice selectedAudioDevice,
+                final Set<CallIntegration.AudioDevice> availableAudioDevices) {
+            mXmppConnectionService.notifyJingleRtpConnectionUpdate(
+                    selectedAudioDevice, availableAudioDevices);
+        }
 
         @Override
         public void onCallIntegrationReject() {}

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

@@ -30,6 +30,7 @@ import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.crypto.axolotl.CryptoFailedException;
 import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
+import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Conversational;
 import eu.siacs.conversations.entities.Message;
@@ -68,7 +69,7 @@ import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 public class JingleRtpConnection extends AbstractJingleConnection
-        implements WebRTCWrapper.EventCallback, CallIntegration.Callback {
+        implements WebRTCWrapper.EventCallback, CallIntegration.Callback, OngoingRtpSession {
 
     public static final List<State> STATES_SHOWING_ONGOING_CALL =
             Arrays.asList(
@@ -2645,6 +2646,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
         return this.sessionDuration.elapsed(TimeUnit.MILLISECONDS);
     }
 
+    @Override
     public CallIntegration getCallIntegration() {
         return this.callIntegration;
     }
@@ -2870,6 +2872,21 @@ public class JingleRtpConnection extends AbstractJingleConnection
         return remoteHasFeature(Namespace.SDP_OFFER_ANSWER);
     }
 
+    @Override
+    public Account getAccount() {
+        return id.account;
+    }
+
+    @Override
+    public Jid getWith() {
+        return id.with;
+    }
+
+    @Override
+    public String getSessionId() {
+        return id.sessionId;
+    }
+
     private interface OnIceServersDiscovered {
         void onIceServersDiscovered(List<PeerConnection.IceServer> iceServers);
     }

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

@@ -1,10 +1,19 @@
 package eu.siacs.conversations.xmpp.jingle;
 
 import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.CallIntegration;
 import eu.siacs.conversations.xmpp.Jid;
 
+import java.util.Set;
+
 public interface OngoingRtpSession {
     Account getAccount();
+
     Jid getWith();
+
     String getSessionId();
+
+    CallIntegration getCallIntegration();
+
+    Set<Media> getMedia();
 }