do not enforce main thread for getting audio devices

fixes #206
This commit is contained in:
Daniel Gultsch 2024-02-23 17:58:33 +01:00
parent 9386769409
commit bd2b9b414e
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2

View file

@ -15,42 +15,31 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo; import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import org.webrtc.ThreadUtils; import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.AppRTCUtils; import eu.siacs.conversations.utils.AppRTCUtils;
import eu.siacs.conversations.xmpp.jingle.Media;
/** import org.webrtc.ThreadUtils;
* AppRTCAudioManager manages all audio related parts of the AppRTC demo.
*/ import java.util.HashSet;
import java.util.Set;
/** AppRTCAudioManager manages all audio related parts of the AppRTC demo. */
public class AppRTCAudioManager { public class AppRTCAudioManager {
private static CountDownLatch microphoneLatch;
private final Context apprtcContext; private final Context apprtcContext;
// Contains speakerphone setting: auto, true or false // Contains speakerphone setting: auto, true or false
// Handles all tasks related to Bluetooth headset devices. // Handles all tasks related to Bluetooth headset devices.
private final AppRTCBluetoothManager bluetoothManager; private final AppRTCBluetoothManager bluetoothManager;
@Nullable @Nullable private final AudioManager audioManager;
private final AudioManager audioManager; @Nullable private AudioManagerEvents audioManagerEvents;
@Nullable
private AudioManagerEvents audioManagerEvents;
private AudioManagerState amState; private AudioManagerState amState;
private boolean savedIsSpeakerPhoneOn; private boolean savedIsSpeakerPhoneOn;
private boolean savedIsMicrophoneMute; private boolean savedIsMicrophoneMute;
@ -76,8 +65,7 @@ public class AppRTCAudioManager {
// Broadcast receiver for wired headset intent broadcasts. // Broadcast receiver for wired headset intent broadcasts.
private final BroadcastReceiver wiredHeadsetReceiver; private final BroadcastReceiver wiredHeadsetReceiver;
// Callback method for changes in audio focus. // Callback method for changes in audio focus.
@Nullable @Nullable private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
public AppRTCAudioManager(final Context context) { public AppRTCAudioManager(final Context context) {
apprtcContext = context; apprtcContext = context;
@ -95,7 +83,6 @@ public class AppRTCAudioManager {
AppRTCUtils.logDeviceInfo(Config.LOGTAG); AppRTCUtils.logDeviceInfo(Config.LOGTAG);
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void start(final AudioManagerEvents audioManagerEvents) { public void start(final AudioManagerEvents audioManagerEvents) {
Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".start()"); Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".start()");
@ -104,7 +91,6 @@ public class AppRTCAudioManager {
Log.e(Config.LOGTAG, "AudioManager is already active"); Log.e(Config.LOGTAG, "AudioManager is already active");
return; return;
} }
awaitMicrophoneLatch();
this.audioManagerEvents = audioManagerEvents; this.audioManagerEvents = audioManagerEvents;
amState = AudioManagerState.RUNNING; amState = AudioManagerState.RUNNING;
// Store current audio state so we can restore it when stop() is called. // Store current audio state so we can restore it when stop() is called.
@ -112,48 +98,45 @@ public class AppRTCAudioManager {
savedIsMicrophoneMute = audioManager.isMicrophoneMute(); savedIsMicrophoneMute = audioManager.isMicrophoneMute();
hasWiredHeadset = hasWiredHeadset(); hasWiredHeadset = hasWiredHeadset();
// Create an AudioManager.OnAudioFocusChangeListener instance. // Create an AudioManager.OnAudioFocusChangeListener instance.
audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { audioFocusChangeListener =
// Called on the listener to notify if the audio focus for this listener has been changed. new AudioManager.OnAudioFocusChangeListener() {
// The |focusChange| value indicates whether the focus was gained, whether the focus was lost, // Called on the listener to notify if the audio focus for this listener has
// and whether that loss is transient, or whether the new focus holder will hold it for an // been changed.
// unknown amount of time. // The |focusChange| value indicates whether the focus was gained, whether the
// TODO(henrika): possibly extend support of handling audio-focus changes. Only contains // focus was lost,
// logging for now. // and whether that loss is transient, or whether the new focus holder will hold
@Override // it for an
public void onAudioFocusChange(int focusChange) { // unknown amount of time.
final String typeOfChange; // TODO(henrika): possibly extend support of handling audio-focus changes. Only
switch (focusChange) { // contains
case AudioManager.AUDIOFOCUS_GAIN: // logging for now.
typeOfChange = "AUDIOFOCUS_GAIN"; @Override
break; public void onAudioFocusChange(final int focusChange) {
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: final String typeOfChange =
typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT"; switch (focusChange) {
break; case AudioManager.AUDIOFOCUS_GAIN -> "AUDIOFOCUS_GAIN";
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: case AudioManager
typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE"; .AUDIOFOCUS_GAIN_TRANSIENT -> "AUDIOFOCUS_GAIN_TRANSIENT";
break; case AudioManager
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: .AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -> "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE";
typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK"; case AudioManager
break; .AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -> "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK";
case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_LOSS -> "AUDIOFOCUS_LOSS";
typeOfChange = "AUDIOFOCUS_LOSS"; case AudioManager
break; .AUDIOFOCUS_LOSS_TRANSIENT -> "AUDIOFOCUS_LOSS_TRANSIENT";
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager
typeOfChange = "AUDIOFOCUS_LOSS_TRANSIENT"; .AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK";
break; default -> "AUDIOFOCUS_INVALID";
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: };
typeOfChange = "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"; Log.d(Config.LOGTAG, "onAudioFocusChange: " + typeOfChange);
break; }
default: };
typeOfChange = "AUDIOFOCUS_INVALID";
break;
}
Log.d(Config.LOGTAG, "onAudioFocusChange: " + typeOfChange);
}
};
// Request audio playout focus (without ducking) and install listener for changes in focus. // Request audio playout focus (without ducking) and install listener for changes in focus.
int result = audioManager.requestAudioFocus(audioFocusChangeListener, int result =
AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); audioManager.requestAudioFocus(
audioFocusChangeListener,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(Config.LOGTAG, "Audio focus request granted for VOICE_CALL streams"); Log.d(Config.LOGTAG, "Audio focus request granted for VOICE_CALL streams");
} else { } else {
@ -182,21 +165,9 @@ public class AppRTCAudioManager {
Log.d(Config.LOGTAG, "AudioManager started"); Log.d(Config.LOGTAG, "AudioManager started");
} }
private void awaitMicrophoneLatch() {
final CountDownLatch latch = microphoneLatch;
if (latch == null) {
return;
}
try {
latch.await();
} catch (InterruptedException e) {
//ignore
}
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void stop() { public void stop() {
Log.d(Config.LOGTAG,"appRtpAudioManager.stop()"); Log.d(Config.LOGTAG, "appRtpAudioManager.stop()");
Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".stop()"); Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".stop()");
ThreadUtils.checkIsOnMainThread(); ThreadUtils.checkIsOnMainThread();
if (amState != AudioManagerState.RUNNING) { if (amState != AudioManagerState.RUNNING) {
@ -214,60 +185,44 @@ public class AppRTCAudioManager {
audioManager.abandonAudioFocus(audioFocusChangeListener); audioManager.abandonAudioFocus(audioFocusChangeListener);
audioFocusChangeListener = null; audioFocusChangeListener = null;
audioManagerEvents = null; audioManagerEvents = null;
Log.d(Config.LOGTAG,"appRtpAudioManager.stopped()"); Log.d(Config.LOGTAG, "appRtpAudioManager.stopped()");
} }
/** /** Changes selection of the currently active audio device. */
* Changes selection of the currently active audio device. private void setAudioDeviceInternal(final CallIntegration.AudioDevice device) {
*/
private void setAudioDeviceInternal(CallIntegration.AudioDevice device) {
Log.d(Config.LOGTAG, "setAudioDeviceInternal(device=" + device + ")"); Log.d(Config.LOGTAG, "setAudioDeviceInternal(device=" + device + ")");
AppRTCUtils.assertIsTrue(audioDevices.contains(device)); AppRTCUtils.assertIsTrue(audioDevices.contains(device));
switch (device) { switch (device) {
case SPEAKER_PHONE: case SPEAKER_PHONE -> setSpeakerphoneOn(true);
setSpeakerphoneOn(true); case EARPIECE, WIRED_HEADSET, BLUETOOTH -> setSpeakerphoneOn(false);
break; default -> Log.e(Config.LOGTAG, "Invalid audio device selection");
case EARPIECE:
case WIRED_HEADSET:
case BLUETOOTH:
setSpeakerphoneOn(false);
break;
default:
Log.e(Config.LOGTAG, "Invalid audio device selection");
break;
} }
selectedAudioDevice = device; selectedAudioDevice = device;
} }
/** /**
* Changes default audio device. * Changes default audio device. TODO(henrika): add usage of this method in the AppRTCMobile
* TODO(henrika): add usage of this method in the AppRTCMobile client. * client.
*/ */
public void setDefaultAudioDevice(CallIntegration.AudioDevice defaultDevice) { public void setDefaultAudioDevice(final CallIntegration.AudioDevice defaultDevice) {
ThreadUtils.checkIsOnMainThread(); ThreadUtils.checkIsOnMainThread();
switch (defaultDevice) { switch (defaultDevice) {
case SPEAKER_PHONE: case SPEAKER_PHONE -> defaultAudioDevice = defaultDevice;
defaultAudioDevice = defaultDevice; case EARPIECE -> {
break;
case EARPIECE:
if (hasEarpiece()) { if (hasEarpiece()) {
defaultAudioDevice = defaultDevice; defaultAudioDevice = defaultDevice;
} else { } else {
defaultAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE; defaultAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
} }
break; }
default: default -> Log.e(Config.LOGTAG, "Invalid default audio device selection");
Log.e(Config.LOGTAG, "Invalid default audio device selection");
break;
} }
Log.d(Config.LOGTAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")"); Log.d(Config.LOGTAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
updateAudioDeviceState(); updateAudioDeviceState();
} }
/** /** Changes selection of the currently active audio device. */
* Changes selection of the currently active audio device. public void selectAudioDevice(final CallIntegration.AudioDevice device) {
*/
public void selectAudioDevice(CallIntegration.AudioDevice device) {
ThreadUtils.checkIsOnMainThread(); ThreadUtils.checkIsOnMainThread();
if (!audioDevices.contains(device)) { if (!audioDevices.contains(device)) {
Log.e(Config.LOGTAG, "Can not select " + device + " from available " + audioDevices); Log.e(Config.LOGTAG, "Can not select " + device + " from available " + audioDevices);
@ -276,38 +231,27 @@ public class AppRTCAudioManager {
updateAudioDeviceState(); updateAudioDeviceState();
} }
/** /** Returns current set of available/selectable audio devices. */
* Returns current set of available/selectable audio devices.
*/
public Set<CallIntegration.AudioDevice> getAudioDevices() { public Set<CallIntegration.AudioDevice> getAudioDevices() {
ThreadUtils.checkIsOnMainThread(); return ImmutableSet.copyOf(audioDevices);
return Collections.unmodifiableSet(new HashSet<>(audioDevices));
} }
/** /** Returns the currently selected audio device. */
* Returns the currently selected audio device.
*/
public CallIntegration.AudioDevice getSelectedAudioDevice() { public CallIntegration.AudioDevice getSelectedAudioDevice() {
return selectedAudioDevice; return selectedAudioDevice;
} }
/** /** Helper method for receiver registration. */
* Helper method for receiver registration.
*/
private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
apprtcContext.registerReceiver(receiver, filter); apprtcContext.registerReceiver(receiver, filter);
} }
/** /** Helper method for unregistration of an existing receiver. */
* Helper method for unregistration of an existing receiver.
*/
private void unregisterReceiver(BroadcastReceiver receiver) { private void unregisterReceiver(BroadcastReceiver receiver) {
apprtcContext.unregisterReceiver(receiver); apprtcContext.unregisterReceiver(receiver);
} }
/** /** Sets the speaker phone mode. */
* Sets the speaker phone mode.
*/
private void setSpeakerphoneOn(boolean on) { private void setSpeakerphoneOn(boolean on) {
boolean wasOn = audioManager.isSpeakerphoneOn(); boolean wasOn = audioManager.isSpeakerphoneOn();
if (wasOn == on) { if (wasOn == on) {
@ -316,9 +260,7 @@ public class AppRTCAudioManager {
audioManager.setSpeakerphoneOn(on); audioManager.setSpeakerphoneOn(on);
} }
/** /** Sets the microphone mute state. */
* Sets the microphone mute state.
*/
private void setMicrophoneMute(boolean on) { private void setMicrophoneMute(boolean on) {
boolean wasMuted = audioManager.isMicrophoneMute(); boolean wasMuted = audioManager.isMicrophoneMute();
if (wasMuted == on) { if (wasMuted == on) {
@ -327,53 +269,57 @@ public class AppRTCAudioManager {
audioManager.setMicrophoneMute(on); audioManager.setMicrophoneMute(on);
} }
/** /** Gets the current earpiece state. */
* Gets the current earpiece state.
*/
private boolean hasEarpiece() { private boolean hasEarpiece() {
return apprtcContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); return apprtcContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
} }
/** /**
* Checks whether a wired headset is connected or not. * Checks whether a wired headset is connected or not. This is not a valid indication that audio
* This is not a valid indication that audio playback is actually over * playback is actually over the wired headset as audio routing depends on other conditions. We
* the wired headset as audio routing depends on other conditions. We * only use it as an early indicator (during initialization) of an attached wired headset.
* only use it as an early indicator (during initialization) of an attached
* wired headset.
*/ */
@Deprecated @Deprecated
private boolean hasWiredHeadset() { private boolean hasWiredHeadset() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
return audioManager.isWiredHeadsetOn(); for (AudioDeviceInfo device : devices) {
} else { final int type = device.getType();
final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL); if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
for (AudioDeviceInfo device : devices) { Log.d(Config.LOGTAG, "hasWiredHeadset: found wired headset");
final int type = device.getType(); return true;
if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) { } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
Log.d(Config.LOGTAG, "hasWiredHeadset: found wired headset"); Log.d(Config.LOGTAG, "hasWiredHeadset: found USB audio device");
return true; return true;
} else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
Log.d(Config.LOGTAG, "hasWiredHeadset: found USB audio device");
return true;
}
} }
return false;
} }
return false;
} }
/** /**
* Updates list of possible audio devices and make new device selection. * Updates list of possible audio devices and make new device selection. TODO(henrika): add unit
* TODO(henrika): add unit test to verify all state transitions. * test to verify all state transitions.
*/ */
public void updateAudioDeviceState() { public void updateAudioDeviceState() {
ThreadUtils.checkIsOnMainThread(); ThreadUtils.checkIsOnMainThread();
Log.d(Config.LOGTAG, "--- updateAudioDeviceState: " Log.d(
+ "wired headset=" + hasWiredHeadset + ", " Config.LOGTAG,
+ "BT state=" + bluetoothManager.getState()); "--- updateAudioDeviceState: "
Log.d(Config.LOGTAG, "Device status: " + "wired headset="
+ "available=" + audioDevices + ", " + hasWiredHeadset
+ "selected=" + selectedAudioDevice + ", " + ", "
+ "user selected=" + userSelectedAudioDevice); + "BT state="
+ bluetoothManager.getState());
Log.d(
Config.LOGTAG,
"Device status: "
+ "available="
+ audioDevices
+ ", "
+ "selected="
+ selectedAudioDevice
+ ", "
+ "user selected="
+ userSelectedAudioDevice);
// Check if any Bluetooth headset is connected. The internal BT state will // Check if any Bluetooth headset is connected. The internal BT state will
// change accordingly. // change accordingly.
// TODO(henrika): perhaps wrap required state into BT manager. // TODO(henrika): perhaps wrap required state into BT manager.
@ -410,12 +356,14 @@ public class AppRTCAudioManager {
// If BT is not available, it can't be the user selection. // If BT is not available, it can't be the user selection.
userSelectedAudioDevice = CallIntegration.AudioDevice.NONE; userSelectedAudioDevice = CallIntegration.AudioDevice.NONE;
} }
if (hasWiredHeadset && userSelectedAudioDevice == CallIntegration.AudioDevice.SPEAKER_PHONE) { if (hasWiredHeadset
&& userSelectedAudioDevice == CallIntegration.AudioDevice.SPEAKER_PHONE) {
// If user selected speaker phone, but then plugged wired headset then make // If user selected speaker phone, but then plugged wired headset then make
// wired headset as user selected device. // wired headset as user selected device.
userSelectedAudioDevice = CallIntegration.AudioDevice.WIRED_HEADSET; userSelectedAudioDevice = CallIntegration.AudioDevice.WIRED_HEADSET;
} }
if (!hasWiredHeadset && userSelectedAudioDevice == CallIntegration.AudioDevice.WIRED_HEADSET) { if (!hasWiredHeadset
&& userSelectedAudioDevice == CallIntegration.AudioDevice.WIRED_HEADSET) {
// If user selected wired headset, but then unplugged wired headset then make // If user selected wired headset, but then unplugged wired headset then make
// speaker phone as user selected device. // speaker phone as user selected device.
userSelectedAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE; userSelectedAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
@ -425,20 +373,30 @@ public class AppRTCAudioManager {
boolean needBluetoothAudioStart = boolean needBluetoothAudioStart =
bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
&& (userSelectedAudioDevice == CallIntegration.AudioDevice.NONE && (userSelectedAudioDevice == CallIntegration.AudioDevice.NONE
|| userSelectedAudioDevice == CallIntegration.AudioDevice.BLUETOOTH); || userSelectedAudioDevice
== CallIntegration.AudioDevice.BLUETOOTH);
// Need to stop Bluetooth audio if user selected different device and // Need to stop Bluetooth audio if user selected different device and
// Bluetooth SCO connection is established or in the process. // Bluetooth SCO connection is established or in the process.
boolean needBluetoothAudioStop = boolean needBluetoothAudioStop =
(bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING) || bluetoothManager.getState()
== AppRTCBluetoothManager.State.SCO_CONNECTING)
&& (userSelectedAudioDevice != CallIntegration.AudioDevice.NONE && (userSelectedAudioDevice != CallIntegration.AudioDevice.NONE
&& userSelectedAudioDevice != CallIntegration.AudioDevice.BLUETOOTH); && userSelectedAudioDevice
!= CallIntegration.AudioDevice.BLUETOOTH);
if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) { || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) {
Log.d(Config.LOGTAG, "Need BT audio: start=" + needBluetoothAudioStart + ", " Log.d(
+ "stop=" + needBluetoothAudioStop + ", " Config.LOGTAG,
+ "BT state=" + bluetoothManager.getState()); "Need BT audio: start="
+ needBluetoothAudioStart
+ ", "
+ "stop="
+ needBluetoothAudioStop
+ ", "
+ "BT state="
+ bluetoothManager.getState());
} }
// Start or stop Bluetooth SCO connection given states set earlier. // Start or stop Bluetooth SCO connection given states set earlier.
if (needBluetoothAudioStop) { if (needBluetoothAudioStop) {
@ -467,7 +425,8 @@ public class AppRTCAudioManager {
} else { } else {
// No wired headset and no Bluetooth, hence the audio-device list can contain speaker // No wired headset and no Bluetooth, hence the audio-device list can contain speaker
// phone (on a tablet), or speaker phone and earpiece (on mobile phone). // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
// |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or AudioDevice.EARPIECE // |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or
// AudioDevice.EARPIECE
// depending on the user's selection. // depending on the user's selection.
newAudioDevice = defaultAudioDevice; newAudioDevice = defaultAudioDevice;
} }
@ -475,9 +434,14 @@ public class AppRTCAudioManager {
if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) { if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) {
// Do the required device switch. // Do the required device switch.
setAudioDeviceInternal(newAudioDevice); setAudioDeviceInternal(newAudioDevice);
Log.d(Config.LOGTAG, "New device status: " Log.d(
+ "available=" + audioDevices + ", " Config.LOGTAG,
+ "selected=" + newAudioDevice); "New device status: "
+ "available="
+ audioDevices
+ ", "
+ "selected="
+ newAudioDevice);
if (audioManagerEvents != null) { if (audioManagerEvents != null) {
// Notify a listening client that audio device has been changed. // Notify a listening client that audio device has been changed.
audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices); audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices);
@ -490,22 +454,19 @@ public class AppRTCAudioManager {
ContextCompat.getMainExecutor(apprtcContext).execute(runnable); ContextCompat.getMainExecutor(apprtcContext).execute(runnable);
} }
/** /** AudioManager state. */
* AudioManager state.
*/
public enum AudioManagerState { public enum AudioManagerState {
UNINITIALIZED, UNINITIALIZED,
PREINITIALIZED, PREINITIALIZED,
RUNNING, RUNNING,
} }
/** /** Selected audio device change event. */
* Selected audio device change event.
*/
public interface AudioManagerEvents { public interface AudioManagerEvents {
// Callback fired once audio device is changed or list of available audio devices changed. // Callback fired once audio device is changed or list of available audio devices changed.
void onAudioDeviceChanged( void onAudioDeviceChanged(
CallIntegration.AudioDevice selectedAudioDevice, Set<CallIntegration.AudioDevice> availableAudioDevices); CallIntegration.AudioDevice selectedAudioDevice,
Set<CallIntegration.AudioDevice> availableAudioDevices);
} }
/* Receiver which handles changes in wired headset availability. */ /* Receiver which handles changes in wired headset availability. */
@ -520,11 +481,21 @@ public class AppRTCAudioManager {
int state = intent.getIntExtra("state", STATE_UNPLUGGED); int state = intent.getIntExtra("state", STATE_UNPLUGGED);
int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
String name = intent.getStringExtra("name"); String name = intent.getStringExtra("name");
Log.d(Config.LOGTAG, "WiredHeadsetReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": " Log.d(
+ "a=" + intent.getAction() + ", s=" Config.LOGTAG,
+ (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m=" "WiredHeadsetReceiver.onReceive"
+ (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb=" + AppRTCUtils.getThreadInfo()
+ isInitialStickyBroadcast()); + ": "
+ "a="
+ intent.getAction()
+ ", s="
+ (state == STATE_UNPLUGGED ? "unplugged" : "plugged")
+ ", m="
+ (microphone == HAS_MIC ? "mic" : "no mic")
+ ", n="
+ name
+ ", sb="
+ isInitialStickyBroadcast());
hasWiredHeadset = (state == STATE_PLUGGED); hasWiredHeadset = (state == STATE_PLUGGED);
updateAudioDeviceState(); updateAudioDeviceState();
} }