diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutputToggleButton.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutputToggleButton.java index f4c1fd6f21..33960f1801 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutputToggleButton.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcAudioOutputToggleButton.java @@ -55,7 +55,7 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView { List availableModes = buildOutputModeList(isHeadsetAvailable, isHandsetAvailable); if (availableModes.size() > 2 || !isHandsetAvailable) showPicker(availableModes); - else setAudioOutput(OUTPUT_MODES.get((outputIndex + 1) % OUTPUT_MODES.size())); + else setAudioOutput(OUTPUT_MODES.get((outputIndex + 1) % OUTPUT_MODES.size()), true); }); } @@ -83,13 +83,16 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView { this.isHeadsetAvailable = isHeadsetAvailable; } - public void setAudioOutput(@NonNull WebRtcAudioOutput audioOutput) { + public void setAudioOutput(@NonNull WebRtcAudioOutput audioOutput, boolean notifyListener) { int oldIndex = outputIndex; outputIndex = resolveAudioOutputIndex(OUTPUT_MODES.lastIndexOf(audioOutput)); if (oldIndex != outputIndex) { refreshDrawableState(); - notifyListener(); + + if (notifyListener) { + notifyListener(); + } } } @@ -100,7 +103,7 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView { private void showPicker(@NonNull List availableModes) { RecyclerView rv = new RecyclerView(getContext()); AudioOutputAdapter adapter = new AudioOutputAdapter(audioOutput -> { - setAudioOutput(audioOutput); + setAudioOutput(audioOutput, true); hidePicker(); }, availableModes); @@ -138,7 +141,8 @@ public class WebRtcAudioOutputToggleButton extends AppCompatImageView { isHandsetAvailable = savedState.getBoolean(STATE_HANDSET_ENABLED); setAudioOutput(OUTPUT_MODES.get( - resolveAudioOutputIndex(savedState.getInt(STATE_OUTPUT_INDEX))) + resolveAudioOutputIndex(savedState.getInt(STATE_OUTPUT_INDEX))), + false ); super.onRestoreInstanceState(savedState.getParcelable(STATE_PARENT)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java index 7c28b0c8c1..2b9e478702 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java @@ -196,10 +196,6 @@ public class WebRtcCallView extends FrameLayout { micToggle.setChecked(isMicEnabled, false); } - public void setAudioOutput(WebRtcAudioOutput output) { - audioToggle.setAudioOutput(output); - } - public void setRemoteVideoEnabled(boolean isRemoteVideoEnabled) { if (isRemoteVideoEnabled) { remoteRenderContainer.setVisibility(View.VISIBLE); @@ -333,7 +329,7 @@ public class WebRtcCallView extends FrameLayout { audioToggle.setControlAvailability(webRtcControls.enableHandsetInAudioToggle(), webRtcControls.enableHeadsetInAudioToggle()); - audioToggle.setAudioOutput(webRtcControls.getAudioOutput()); + audioToggle.setAudioOutput(webRtcControls.getAudioOutput(), false); } if (webRtcControls.displayCameraToggle()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java index ad52e361b7..60e56d353c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -482,10 +482,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, boolean isSpeaker = intent.getBooleanExtra(EXTRA_SPEAKER, false); AudioManager audioManager = ServiceUtil.getAudioManager(this); - audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); - audioManager.stopBluetoothSco(); - audioManager.setBluetoothScoOn(false); - audioManager.setSpeakerphoneOn(true); + bluetoothStateManager.setWantsConnection(false); audioManager.setSpeakerphoneOn(isSpeaker); if (!localCameraState.isEnabled()) { @@ -499,15 +496,8 @@ public class WebRtcCallService extends Service implements CallManager.Observer, private void handleSetBluetoothAudio(Intent intent) { boolean isBluetooth = intent.getBooleanExtra(EXTRA_BLUETOOTH, false); - AudioManager audioManager = ServiceUtil.getAudioManager(this); - if (isBluetooth) { - audioManager.startBluetoothSco(); - audioManager.setBluetoothScoOn(true); - } else { - audioManager.stopBluetoothSco(); - audioManager.setBluetoothScoOn(false); - } + bluetoothStateManager.setWantsConnection(isBluetooth); if (!localCameraState.isEnabled()) { lockManager.updatePhoneState(getInCallPhoneState()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCompat.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCompat.java new file mode 100644 index 0000000000..d31ed35b66 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCompat.java @@ -0,0 +1,143 @@ +package org.thoughtcrime.securesms.webrtc.audio; + +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioFocusRequest; +import android.media.AudioManager; +import android.media.SoundPool; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import org.thoughtcrime.securesms.util.ServiceUtil; + +public abstract class AudioManagerCompat { + + protected final AudioManager audioManager; + + private AudioManagerCompat(@NonNull Context context) { + audioManager = ServiceUtil.getAudioManager(context); + } + + abstract public SoundPool createSoundPool(); + abstract public void requestCallAudioFocus(); + abstract public void abandonCallAudioFocus(); + + public static AudioManagerCompat create(@NonNull Context context) { + if (Build.VERSION.SDK_INT >= 26) { + return new Api26AudioManagerCompat(context); + } else if (Build.VERSION.SDK_INT >= 21) { + return new Api21AudioManagerCompat(context); + } else { + return new Api19AudioManagerCompat(context); + } + } + + @RequiresApi(26) + private static class Api26AudioManagerCompat extends AudioManagerCompat { + + private static AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .build(); + + private AudioFocusRequest audioFocusRequest; + + private Api26AudioManagerCompat(@NonNull Context context) { + super(context); + } + + @Override + public SoundPool createSoundPool() { + return new SoundPool.Builder() + .setAudioAttributes(AUDIO_ATTRIBUTES) + .setMaxStreams(1) + .build(); + } + + @Override + public void requestCallAudioFocus() { + if (audioFocusRequest != null) { + throw new IllegalStateException("Already focused."); + } + + audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT) + .setAudioAttributes(AUDIO_ATTRIBUTES) + .build(); + + int result = audioManager.requestAudioFocus(audioFocusRequest); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + throw new IllegalStateException("Got " + result); + } + } + + @Override + public void abandonCallAudioFocus() { + if (audioFocusRequest == null) { + throw new IllegalStateException("Not focused."); + } + + int result = audioManager.abandonAudioFocusRequest(audioFocusRequest); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + throw new IllegalStateException("Got " + result); + } + + audioFocusRequest = null; + } + } + + @RequiresApi(21) + private static class Api21AudioManagerCompat extends Api19AudioManagerCompat { + + private static AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL) + .build(); + + private Api21AudioManagerCompat(@NonNull Context context) { + super(context); + } + + @Override + public SoundPool createSoundPool() { + return new SoundPool.Builder() + .setAudioAttributes(AUDIO_ATTRIBUTES) + .setMaxStreams(1) + .build(); + } + } + + private static class Api19AudioManagerCompat extends AudioManagerCompat { + + private Api19AudioManagerCompat(@NonNull Context context) { + super(context); + } + + @Override + public SoundPool createSoundPool() { + return new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 0); + } + + @Override + public void requestCallAudioFocus() { + int result = audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + throw new IllegalStateException("Got " + result); + } + } + + @Override + public void abandonCallAudioFocus() { + int result = audioManager.abandonAudioFocus(null); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + throw new IllegalStateException("Got " + result); + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/BluetoothStateManager.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/BluetoothStateManager.java index 853ae15fcb..a6bda71bc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/BluetoothStateManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/BluetoothStateManager.java @@ -23,7 +23,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class BluetoothStateManager { - private static final String TAG = BluetoothStateManager.class.getSimpleName(); + private static final String TAG = Log.tag(BluetoothStateManager.class); private enum ScoConnection { DISCONNECTED, diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java index dfb8fb33a6..37e0d40143 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.webrtc.audio; import android.annotation.TargetApi; import android.content.Context; +import android.media.AudioAttributes; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; @@ -109,7 +110,15 @@ public class IncomingRinger { mediaPlayer.setOnErrorListener(new MediaPlayerErrorListener()); mediaPlayer.setDataSource(context, ringtoneUri); mediaPlayer.setLooping(true); - mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING); + + if (Build.VERSION.SDK_INT <= 21) { + mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING); + } else { + mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) + .build()); + } return mediaPlayer; } catch (IOException e) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/OutgoingRinger.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/OutgoingRinger.java index 1c17811d9e..b9c439d1c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/OutgoingRinger.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/OutgoingRinger.java @@ -1,9 +1,12 @@ package org.thoughtcrime.securesms.webrtc.audio; import android.content.Context; +import android.media.AudioAttributes; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; +import android.os.Build; + import androidx.annotation.NonNull; import org.thoughtcrime.securesms.logging.Log; @@ -40,7 +43,15 @@ public class OutgoingRinger { } mediaPlayer = new MediaPlayer(); - mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL); + + if (Build.VERSION.SDK_INT <= 21) { + mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL); + } else { + mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .build()); + } mediaPlayer.setLooping(true); String packageName = context.getPackageName(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.java index 5b9d6d2a56..cd2c4e19b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.java @@ -25,19 +25,21 @@ public class SignalAudioManager { private final int connectedSoundId; private final int disconnectedSoundId; + private final AudioManagerCompat audioManagerCompat; + public SignalAudioManager(@NonNull Context context) { - this.context = context.getApplicationContext(); - this.incomingRinger = new IncomingRinger(context); - this.outgoingRinger = new OutgoingRinger(context); - this.soundPool = new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 0); + this.context = context.getApplicationContext(); + this.incomingRinger = new IncomingRinger(context); + this.outgoingRinger = new OutgoingRinger(context); + this.audioManagerCompat = AudioManagerCompat.create(context); + this.soundPool = audioManagerCompat.createSoundPool(); this.connectedSoundId = this.soundPool.load(context, R.raw.webrtc_completed, 1); this.disconnectedSoundId = this.soundPool.load(context, R.raw.webrtc_disconnected, 1); } public void initializeAudioForCall() { - AudioManager audioManager = ServiceUtil.getAudioManager(context); - audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE); + audioManagerCompat.requestCallAudioFocus(); } public void startIncomingRinger(@Nullable Uri ringtoneUri, boolean vibrate) { @@ -89,14 +91,8 @@ public class SignalAudioManager { soundPool.play(disconnectedSoundId, 1.0f, 1.0f, 0, 0, 1.0f); } - if (audioManager.isBluetoothScoOn()) { - audioManager.setBluetoothScoOn(false); - audioManager.stopBluetoothSco(); - } - - audioManager.setSpeakerphoneOn(false); - audioManager.setMicrophoneMute(false); audioManager.setMode(AudioManager.MODE_NORMAL); - audioManager.abandonAudioFocus(null); + + audioManagerCompat.abandonCallAudioFocus(); } }