diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index bdef662bd..7e1b3592e 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -16,6 +16,7 @@ import android.view.View; import android.view.WindowManager; import android.widget.Toast; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.lang.ref.WeakReference; @@ -344,18 +345,91 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe this.binding.endCall.setVisibility(View.VISIBLE); this.binding.acceptCall.setVisibility(View.INVISIBLE); } + updateInCallButtonConfiguration(state); + } + private void updateInCallButtonConfiguration() { + updateInCallButtonConfiguration(requireRtpConnection().getEndUserState()); + } + + @SuppressLint("RestrictedApi") + private void updateInCallButtonConfiguration(final RtpEndUserState state) { if (state == RtpEndUserState.CONNECTED) { - this.binding.inCallActionLeft.setImageResource(R.drawable.ic_volume_off_black_24dp); - this.binding.inCallActionLeft.setVisibility(View.VISIBLE); - this.binding.inCallActionRight.setImageResource(R.drawable.ic_mic_black_24dp); - this.binding.inCallActionRight.setVisibility(View.VISIBLE); + final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager(); + updateInCallButtonConfiguration( + audioManager.getSelectedAudioDevice(), + audioManager.getAudioDevices().size(), + requireRtpConnection().isMicrophoneEnabled() + ); } else { this.binding.inCallActionLeft.setVisibility(View.GONE); this.binding.inCallActionRight.setVisibility(View.GONE); } } + @SuppressLint("RestrictedApi") + private void updateInCallButtonConfiguration(final AppRTCAudioManager.AudioDevice selectedAudioDevice, final int numberOfChoices, final boolean microphoneEnabled) { + switch (selectedAudioDevice) { + case EARPIECE: + this.binding.inCallActionLeft.setImageResource(R.drawable.ic_volume_off_black_24dp); + if (numberOfChoices >= 2) { + this.binding.inCallActionLeft.setOnClickListener(this::switchToSpeaker); + } else { + this.binding.inCallActionLeft.setOnClickListener(null); + this.binding.inCallActionLeft.setClickable(false); + } + break; + case WIRED_HEADSET: + this.binding.inCallActionLeft.setImageResource(R.drawable.ic_headset_black_24dp); + this.binding.inCallActionLeft.setOnClickListener(null); + this.binding.inCallActionLeft.setClickable(false); + break; + case SPEAKER_PHONE: + this.binding.inCallActionLeft.setImageResource(R.drawable.ic_volume_up_black_24dp); + if (numberOfChoices >= 2) { + this.binding.inCallActionLeft.setOnClickListener(this::switchToEarpiece); + } else { + this.binding.inCallActionLeft.setOnClickListener(null); + this.binding.inCallActionLeft.setClickable(false); + } + break; + case BLUETOOTH: + this.binding.inCallActionLeft.setImageResource(R.drawable.ic_bluetooth_audio_black_24dp); + this.binding.inCallActionLeft.setOnClickListener(null); + this.binding.inCallActionLeft.setClickable(false); + break; + } + this.binding.inCallActionLeft.setVisibility(View.VISIBLE); + if (microphoneEnabled) { + this.binding.inCallActionRight.setImageResource(R.drawable.ic_mic_black_24dp); + this.binding.inCallActionRight.setOnClickListener(this::disableMicrophone); + } else { + this.binding.inCallActionRight.setImageResource(R.drawable.ic_mic_off_black_24dp); + this.binding.inCallActionRight.setOnClickListener(this::enableMicrophone); + } + this.binding.inCallActionRight.setVisibility(View.VISIBLE); + } + + private void disableMicrophone(View view) { + JingleRtpConnection rtpConnection = requireRtpConnection(); + rtpConnection.setMicrophoneEnabled(false); + updateInCallButtonConfiguration(); + } + + private void enableMicrophone(View view) { + JingleRtpConnection rtpConnection = requireRtpConnection(); + rtpConnection.setMicrophoneEnabled(true); + updateInCallButtonConfiguration(); + } + + private void switchToEarpiece(View view) { + requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE); + } + + private void switchToSpeaker(View view) { + requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); + } + private void retry(View view) { Log.d(Config.LOGTAG, "attempting retry"); final Intent intent = getIntent(); @@ -419,6 +493,18 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe @Override public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set availableAudioDevices) { Log.d(Config.LOGTAG, "onAudioDeviceChanged in activity: selected:" + selectedAudioDevice + ", available:" + availableAudioDevices); + try { + if (requireRtpConnection().getEndUserState() == RtpEndUserState.CONNECTED) { + final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager(); + updateInCallButtonConfiguration( + audioManager.getSelectedAudioDevice(), + audioManager.getAudioDevices().size(), + requireRtpConnection().isMicrophoneEnabled() + ); + } + } catch (IllegalStateException e) { + Log.d(Config.LOGTAG, "RTP connection was not available when audio device changed"); + } } private void updateRtpSessionProposalState(final Account account, final Jid with, final RtpEndUserState state) { 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 d3dba70a9..b8a67cb0e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -833,6 +833,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } + public AppRTCAudioManager getAudioManager() { + return webRTCWrapper.getAudioManager(); + } + + public void setMicrophoneEnabled(final boolean enabled) { + webRTCWrapper.setMicrophoneEnabled(enabled); + } + + public boolean isMicrophoneEnabled() { + return webRTCWrapper.isMicrophoneEnabled(); + } + @Override public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set availableAudioDevices) { xmppConnectionService.notifyJingleRtpConnectionUpdate(selectedAudioDevice, availableAudioDevices); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 0b776e431..e21a65f19 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -131,6 +131,7 @@ public class WebRTCWrapper { }; @Nullable private PeerConnection peerConnection = null; + private AudioTrack localAudioTrack = null; private AppRTCAudioManager appRTCAudioManager = null; private final Handler mainHandler = new Handler(Looper.getMainLooper()); @@ -201,10 +202,9 @@ public class WebRTCWrapper { final AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints()); - final AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("my-audio-track", audioSource); - Log.d(Config.LOGTAG, "audioTrack enabled:" + audioTrack.enabled() + " state=" + audioTrack.state()); + this.localAudioTrack = peerConnectionFactory.createAudioTrack("my-audio-track", audioSource); final MediaStream stream = peerConnectionFactory.createLocalMediaStream("my-media-stream"); - stream.addTrack(audioTrack); + stream.addTrack(this.localAudioTrack); //stream.addTrack(videoTrack); this.localVideoTrack = videoTrack; @@ -229,6 +229,22 @@ public class WebRTCWrapper { } } + public void setMicrophoneEnabled(final boolean enabled) { + final AudioTrack audioTrack = this.localAudioTrack; + if (audioTrack == null) { + throw new IllegalStateException("Local audio track does not exist (yet)"); + } + audioTrack.setEnabled(enabled); + } + + public boolean isMicrophoneEnabled() { + final AudioTrack audioTrack = this.localAudioTrack; + if (audioTrack == null) { + throw new IllegalStateException("Local audio track does not exist (yet)"); + } + return audioTrack.enabled(); + } + public ListenableFuture createOffer() { return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> { @@ -330,6 +346,10 @@ public class WebRTCWrapper { return peerConnection; } + public AppRTCAudioManager getAudioManager() { + return appRTCAudioManager; + } + private static abstract class SetSdpObserver implements SdpObserver { @Override diff --git a/src/main/res/drawable-hdpi/ic_bluetooth_audio_black_24dp.png b/src/main/res/drawable-hdpi/ic_bluetooth_audio_black_24dp.png new file mode 100644 index 000000000..14a5a5584 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_bluetooth_audio_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_headset_black_24dp.png b/src/main/res/drawable-hdpi/ic_headset_black_24dp.png new file mode 100644 index 000000000..38eb219ef Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_headset_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_bluetooth_audio_black_24dp.png b/src/main/res/drawable-mdpi/ic_bluetooth_audio_black_24dp.png new file mode 100644 index 000000000..5feb0c03a Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_bluetooth_audio_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_headset_black_24dp.png b/src/main/res/drawable-mdpi/ic_headset_black_24dp.png new file mode 100644 index 000000000..d872b05d5 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_headset_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_bluetooth_audio_black_24dp.png b/src/main/res/drawable-xhdpi/ic_bluetooth_audio_black_24dp.png new file mode 100644 index 000000000..c1d6b931d Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_bluetooth_audio_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_headset_black_24dp.png b/src/main/res/drawable-xhdpi/ic_headset_black_24dp.png new file mode 100644 index 000000000..f2664dcde Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_headset_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_bluetooth_audio_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_bluetooth_audio_black_24dp.png new file mode 100644 index 000000000..5fff1521b Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_bluetooth_audio_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_headset_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_headset_black_24dp.png new file mode 100644 index 000000000..baf3ee295 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_headset_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_bluetooth_audio_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_bluetooth_audio_black_24dp.png new file mode 100644 index 000000000..2fc2be2ae Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_bluetooth_audio_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_headset_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_headset_black_24dp.png new file mode 100644 index 000000000..974457ee1 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_headset_black_24dp.png differ