diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index bdef662bde53f0c32880161233710ad34daedcc7..7e1b3592e305f498af4f3037d58e5b696a7bac36 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 d3dba70a9eb7560db8be7aaa3c05cb15bbcdcdcd..b8a67cb0e76cd63f2ab40fdff0ff048d796cc9fb 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 0b776e431d53a2fce695436dd32a44b1dc645f5a..e21a65f19bc2d351a5b2867bb49318479a04d24e 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 0000000000000000000000000000000000000000..14a5a5584a2a102a8862f1166bdfe6582989212f 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 0000000000000000000000000000000000000000..38eb219ef8aa003cbc917c2901d73f3742864496 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 0000000000000000000000000000000000000000..5feb0c03a872c53a0c32e5bf6759e18d9a2b097e 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 0000000000000000000000000000000000000000..d872b05d5c54f5bab40e21c182bd41f2124e4dac 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 0000000000000000000000000000000000000000..c1d6b931dc0c7f2a629e528f44e6def0eddd546d 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 0000000000000000000000000000000000000000..f2664dcde2705ebf36252d28c5a116286b9fca14 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 0000000000000000000000000000000000000000..5fff1521bdb545e3b92a94547e2b71ae80c2d28a 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 0000000000000000000000000000000000000000..baf3ee295d5a60b92ef94ed77c52c98d1c7303ae 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 0000000000000000000000000000000000000000..2fc2be2ae5da91395bb1e8dbb225c1605f367b27 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 0000000000000000000000000000000000000000..974457ee1dc25f4c8e68fb4908cd1d5eb4639e0c Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_headset_black_24dp.png differ