how to switch audio output programatically in android 10? - android

My use case is I have an app in which a user can press a button and it would toggle between external speaker and the wired headset/bluetooth device. If the user is connected to a wired headset or a bluetooth ear piece device, I want to switch the audio output from that wired headset or bluetooth to the external speaker, and then when they click again, to renable the wired headset/bluetooth audio output.
Does anyone know this can be done in Android 10? I tried the following the example in this post but it doesnt consistently work for bluetooth cases. My code is as followed:
if (shouldEnableExternalSpeaker) {
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager.isSpeakerphoneOn = true
if (isBlueToothConnected) {
audioManager.stopBluetoothScoOn()
}
} else {
if (isBlueToothConnected) {
audioManager.startBluetoothSco()
}
audioManager.mode = AudioManager.NORMAL
audioManager.isSpeakerphoneOn = false
}
I also have the necessary permission to the user's audio:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

I think you should split your handling into 3 cases, play sound via:
external device via Bluetooth
wired headset/device
phone speaker
The resulting code could then loke like this.
if(shouldEnableExternalSpeaker) {
if(isBlueToothConnected) {
// 1. case - bluetooth device
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.startBluetoothSco();
mAudioManager.setBluetoothScoOn(true);
} else {
// 2. case - wired device
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(false);
}
} else {
// 3. case - phone speaker
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(true);
}
Even though I haven't used the AudioManager recently, this has worked for me in the past.

Lines:
audioManager.isSpeakerphoneOn = true
audioManager.isSpeakerphoneOn = false
will not work. You have to use setMethods:
Case 1 - bluetooth
mAudioManager.startBluetoothSco(); // This method can be used by applications wanting to send and received audio to/from a bluetooth SCO headset while the phone is not in call.
mAudioManager.setBluetoothScoOn(true); // set true to use bluetooth SCO for communications; false to not use bluetooth SCO for communications
Case 2 - wired
mAudioManager.stopBluetoothSco(); // stop wanting to send and receive
mAudioManager.setBluetoothScoOn(false); // turn off bluetooth
mAudioManager.setSpeakerphoneOn(false); //set true to turn on speakerphone; false to turn it off
Case 3 - internal speaker
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(true); // turn on speaker phone
You can keep things automatically happening as follows:
public class BluetoothReceiver extends BroadcastReceiver {
private AudioManager localAudioManager;
private static final int STATE_DISCONNECTED = 0x00000000;
private static final String EXTRA_STATE = "android.bluetooth.headset.extra.STATE";
private static final String TAG = "BluetoothReceiver";
private static final String ACTION_BT_HEADSET_STATE_CHANGED = "android.bluetooth.headset.action.STATE_CHANGED";
private static final String ACTION_BT_HEADSET_FORCE_ON = "android.bluetooth.headset.action.FORCE_ON";
private static final String ACTION_BT_HEADSET_FORCE_OFF = "android.bluetooth.headset.action.FORCE_OFF";
#Override
public void onReceive(final Context context, final Intent intent) {
Log.i(TAG,"onReceive - BluetoothBroadcast");
localAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
final String action = intent.getAction();
if (action.equals(ACTION_BT_HEADSET_STATE_CHANGED)) {
final int extraData = intent.getIntExtra(EXTRA_STATE, STATE_DISCONNECTED);
if (extraData == STATE_DISCONNECTED) {
//no headset -> going other modes
localAudioManager.setBluetoothScoOn(false);
localAudioManager.stopBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_NORMAL);
Log.i(TAG, "Bluetooth Headset Off " + localAudioManager.getMode());
Log.i(TAG, "A2DP: " + localAudioManager.isBluetoothA2dpOn() + ". SCO: " + localAudioManager.isBluetoothScoAvailableOffCall());
} else {
localAudioManager.setMode(0);
localAudioManager.setBluetoothScoOn(true);
localAudioManager.startBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_IN_CALL);
Log.i(TAG, "Bluetooth Headset On " + localAudioManager.getMode());
Log.i(TAG, "A2DP: " + localAudioManager.isBluetoothA2dpOn() + ". SCO: " + localAudioManager.isBluetoothScoAvailableOffCall());
}
}
if (action.equals(ACTION_BT_HEADSET_FORCE_ON)) {
localAudioManager.setMode(0);
localAudioManager.setBluetoothScoOn(true);
localAudioManager.startBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_IN_CALL);
Log.i(TAG, "Bluetooth Headset On " + localAudioManager.getMode());
Log.i(TAG, "A2DP: " + localAudioManager.isBluetoothA2dpOn() + ". SCO: " + localAudioManager.isBluetoothScoAvailableOffCall());
}
if (action.equals(ACTION_BT_HEADSET_FORCE_OFF)) {
localAudioManager.setBluetoothScoOn(false);
localAudioManager.stopBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_NORMAL);
Log.i(TAG, "Bluetooth Headset Off " + localAudioManager.getMode());
Log.i(TAG, "A2DP: " + localAudioManager.isBluetoothA2dpOn() + ". SCO: " + localAudioManager.isBluetoothScoAvailableOffCall());
}
}
}

Related

Android 12 startBluetoothSco not works once setMode to MODE_IN_COMMUNICATION firstly by other app

Subject
startBluetoothSco do not work when audio mode was set to MODE_IN_COMMUNICATION firstly by other app on android 12 or higher.
Question
I want to let startBluetoothSco work even when it has been set to MODE_IN_COMMUNICATION firstly by another app
Steps
My test steps are as follows:
Connect the device with a Bluetooth headset,
open the first app, let the app set the audio mode to MODE_IN_COMMUNICATION, and keep the recorder or player running. Code example:
AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
44100, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
11000);
mAudioRecord.startRecording();
Open the second app, and still set the audio mode to AudioManager.MODE_IN_COMMUNICATION, by the way, startBluetoothSco.
Then the second can not receive any Bluetooth broadcast and SCO does not works.
However, when I kill the first app and rerun the second app, SCO works.
Analysis
According the source code, here is the code:
private CommunicationRouteClient topCommunicationRouteClient() {
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
if (crc.getPid() == mModeOwnerPid) {
return crc;
}
}
if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
return mCommunicationRouteClients.get(0);
}
return null;
}
when the first app sets the mode to MODE_IN_COMMUNICATION and when will become the mode owner
the second app runs startBluetoothSco, and will get null client, and then will do not start SCO.
private void onUpdateCommunicationRoute(String eventSource) {
AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG,
"onUpdateCommunicationRoute, preferredCommunicationDevice: " +
preferredCommunicationDevice +
" eventSource: " +
eventSource);
}
AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
"onUpdateCommunicationRoute, preferredCommunicationDevice: " +
preferredCommunicationDevice + " eventSource: " + eventSource)));
if (preferredCommunicationDevice == null ||
preferredCommunicationDevice.getType() !=
AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
AudioSystem.setParameters("BT_SCO=off");
} else {
AudioSystem.setParameters("BT_SCO=on");
}
if (preferredCommunicationDevice == null) {
removePreferredDevicesForStrategySync(mCommunicationStrategyId);
removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
} else {
setPreferredDevicesForStrategySync(
mCommunicationStrategyId,
Arrays.asList(preferredCommunicationDevice));
setPreferredDevicesForStrategySync(
mAccessibilityStrategyId,
Arrays.asList(preferredCommunicationDevice));
}
onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
}

AudioManager.setCommunicationDevice to TYPE_BUILTIN_SPEAKER not working

I am attempting to implement a Loud Speaker / Earpiece switch in Android 12 using the new AudioManager.setCommunicationDevice in place of the deprecated AudioManager.setSpeakerphoneOn.
The result of the set call is true, but the device does not appear to update (both functionally and by checking AudioManager.getCommunicationDevice before and after).
The application is selected as the default Phone package, AudioManager mode is set to AudioManager.MODE_IN_CALL and the manifest includes
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
Below is a code snippet which shows the effect of trying to just set loud speaker during an active call.
AudioDeviceInfo loudSpeakerAudioDevice = null;
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_CALL);
List<AudioDeviceInfo> audioDevices = audioManager.getAvailableCommunicationDevices();
for (AudioDeviceInfo audioDevice : audioDevices)
{
if (audioDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
{
loudSpeakerAudioDevice = audioDevice;
}
}
Log.i("Demo", "Current AudioDevice = " + audioManager.getCommunicationDevice().toString());
if (audioManager.getCommunicationDevice().getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE)
{
Log.i("Demo", "Setting to loudSpeakerAudioDevice = " + loudSpeakerAudioDevice.toString());
if (audioManager.setCommunicationDevice(loudSpeakerAudioDevice) == false)
{
Log.e("Demo", "Failed to set audio device");
}
Log.i("Demo", "New AudioDevice = " + audioManager.getCommunicationDevice().toString());
}
The output of this shows the device ID is the same before and after the set (21).
2022-07-27 10:30:01.465 27907-27907/? I/Demo: Current AudioDevice = android.media.AudioDeviceInfo#21
2022-07-27 10:30:01.501 27907-27907/? I/Demo: Setting to loudSpeakerAudioDevice = android.media.AudioDeviceInfo#22
2022-07-27 10:30:01.518 27907-27907/? I/Demo: New AudioDevice = android.media.AudioDeviceInfo#21

Android TV box development how to use HDMI cable to transmit audio

I have a problem now, I developed a player on Android TV, when I simulate using the APP on the mobile phone, the mobile phone speaker can play the sound normally, but when I use the APP on the Android TV box, it is not Can't get audio to my TV speakers via HDMI. At the same time, when I use the native app on the TV box to play video, the sound can be streamed to the TV's speakers via HDMI for playback.
In my code, the following permissions are declared:
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
I have a method called initAudioManager defined as follows:
private void initAudioManager() {
CustomSetting audioModeSetting = customSettingMapper.selectCustomSettingByKey(CustomSettingByKey.AUDIO_OUTPUT_MODE.getKey());
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) {
Log.e(TAG, "initAudioManager: audioManager is null");
return;
}
if (audioModeSetting == null) {
audioManager.setParameters("audio_devices_out_active=AUDIO_CODEC");
Log.i(TAG, "initAudioManager: audioModeSetting is null,set audio_devices_out_active=AUDIO_CODEC");
}
if (audioModeSetting != null) {
if (audioModeSetting.getSettingValue() == 0L) {
audioManager.setParameters("audio_devices_out_active=AUDIO_CODEC");
Log.i(TAG, "initAudioManager: audioModeSetting.getSettingValue() == 0L,set audio_devices_out_active=AUDIO_CODEC");
} else {
audioManager.setParameters("audio_devices_out_active=AUDIO_HDMI");
Log.i(TAG, "initAudioManager: audioModeSetting.getSettingValue() == 1L,set audio_devices_out_active=AUDIO_HDMI");
}
}
Log.i(TAG, "initAudioManager: " + audioManager.getParameters("audio_devices_out_active"));
Log.i(TAG, "initAudioManager: " + Arrays.toString(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)));
MediaRouter mediaRouter = (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE);
if (mediaRouter == null) {
Log.e(TAG, "initAudioManager: mediaRouter is null");
return;
}
MediaRouter.RouteInfo selectedRoute = mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO);
Log.d(TAG, "initAudioManager: " + selectedRoute.getName());
}
When the audioModeSetting.getSettingValue() == 0L condition is not met, I will set the device audio output mode to HDMI, but it has no effect.
There is a place to print the log in my code, which is written like this:
Log.i(TAG, "initAudioManager: " + audioManager.getParameters("audio_devices_out_active"));
But when my program runs, the log printed here is empty and nothing.
What should I do to get my audio data to pass through the HDMI cable to the speakers of the TV?

Android: bluetooth change events occur before AudioManager changes

I'm trying to detect when a bluetooth earbud or headset is turned on or turned off. The AudioManager class has these methods: isBluetoothA2dpOn() and isBluetoothScoOn() which seem to work fine with my test devices. These methods accurately return the state of the bluetooth devices.
Next I set up a BroadcastReceiver to get notified for changes in bluetooth connectivity status. Here is the code to setup the BroadcastReceiver:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); // catches bluetooth ON/OFF (the major case)
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); // catches when the actual bt device connects.
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); // does NOT catch devices connected/disconnected
registerReceiver(mBroadcastReceiver, intentFilter);
And here is the BroadcastReceiver:
mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent itt) {
String action = itt.getAction();
switch (action) {
// This just gets notified if bluetooth is turned OFF or ON.
case BluetoothAdapter.ACTION_STATE_CHANGED: {
int extraState = itt.getIntExtra(BluetoothAdapter.EXTRA_STATE, MY_ERROR_STATE);
int extraPreviousState = itt.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, MY_ERROR_STATE);
String str = " bluetooth state changed from " + extraPreviousState + " to " + extraState;
Log.d(TAG, str);
break;
}
case AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED: {
int newState = itt.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, MY_ERROR_STATE);
int prevState = itt.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, MY_ERROR_STATE);
String str = " bluetooth device updated from " + prevState + " to " + newState;
Log.d(TAG, str);
break;
}
case BluetoothDevice.ACTION_ACL_CONNECTED: {
BluetoothDevice device = itt.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String str = " connected to bluetooth device: " + device.getName() + ", " + device.describeContents();
Log.d(TAG, str);
break;
}
case BluetoothDevice.ACTION_ACL_DISCONNECTED: {
BluetoothDevice device = itt.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String str = " disconnected from bluetooth device: " + device.getName() + ", " + device.describeContents();
Log.d(TAG, str);
break;
}
}
Log.d(TAG, "bluetooth sco = " + mAudioMgr.isBluetoothScoOn());
Log.d(TAG, "bluetooth a2dp = " + mAudioMgr.isBluetoothA2dpOn());
}
};
Now the BroadcastReceiver is notified whenever there is a change in the various bluetooth and device states. For example, turning on my bluetooth earbuds causes the ACL_CONNECTED to fire (as expected), with the extra data indicating that it is now in an ON state.
But when the code hits the log statements, bluetooth a2dp will still register as turned OFF. Weird, as the code just received a noticed that it was turned on.
If I wait a few seconds, the mAudioMgr.isBluetoothxxx() will return the correct result.
Anyone have an explanation of this seemingly contradictory behavior?

Disable android internal microphone

I am developing an app that uses voice recognition and I want to disable internal microphone while I use a Bluetooth headset. The problem is that the internal android microphone keep listening and the recognition engine recognize words that I don't want (other peoples speak near phone, or ambiental noise make recognition useful). Thank you!
public class BluetoothHelper extends BluetoothHeadsetUtils {
private Context _mContext;
private AudioManager audioManager;
private int audioModeBackup;
public BluetoothHelper(Context context) {
super(context);
this._mContext = context;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
#Override
public void onScoAudioDisconnected() {
// Cancel speech recognizer if desired
AudioManager audioManager = (AudioManager) _mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.setStreamSolo(AudioManager.USE_DEFAULT_STREAM_TYPE, true);
Log.d(BluetoothHelper.class.getSimpleName(), "A2DP: " + audioManager.isBluetoothA2dpOn() + ". SCO: "
+ audioManager.isBluetoothScoAvailableOffCall());
Toast.makeText(_mContext, "SCO Audio disconnected", Toast.LENGTH_SHORT).show();
}
#Override
public void onScoAudioConnected() {
// Should start speech recognition here if not already started
AudioManager audioManager = (AudioManager) _mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.setStreamSolo(AudioManager.STREAM_VOICE_CALL, true);
Log.d(BluetoothHelper.class.getSimpleName(), "Is bluetooth sco on: "+audioManager.isBluetoothScoOn());
Log.d(BluetoothHelper.class.getSimpleName(), "A2DP: " + audioManager.isBluetoothA2dpOn() + ". SCO: "
+ audioManager.isBluetoothScoAvailableOffCall()+" SCO ON: ");
Toast.makeText(_mContext, "SCO Audio connected. Audio is on headset SCO: " + this.isOnHeadsetSco(),
Toast.LENGTH_SHORT).show();
}
#Override
public void onHeadsetDisconnected() {
AudioManager audioManager = (AudioManager) _mContext.getSystemService(Context.AUDIO_SERVICE);
Log.i(BluetoothHelper.class.getSimpleName(), "A2DP: " + audioManager.isBluetoothA2dpOn() + ". SCO: "
+ audioManager.isBluetoothScoAvailableOffCall());
Toast.makeText(_mContext, "Bluetooth Headset Off", Toast.LENGTH_SHORT).show();
/* Unmute the external microphone */
setInternalMicMute(false);
}
#Override
public void onHeadsetConnected() {
AudioManager audioManager = (AudioManager) _mContext.getSystemService(Context.AUDIO_SERVICE);
if(!audioManager.isBluetoothA2dpOn()){
Log.d("BluetoothHelper", "Reset connection with bluetooth");
this.setStarted(false);
this.start();
}
else{
this.mIsCountDownOn = true;
Log.i(BluetoothHelper.class.getSimpleName(), "A2DP: " + audioManager.isBluetoothA2dpOn() + ". SCO: "
+ audioManager.isBluetoothScoAvailableOffCall());
Toast.makeText(_mContext, "Bluetooth Headset On.", Toast.LENGTH_SHORT).show();
/* Mute the external microphone */
setInternalMicMute(true);
this.mCountDown11.start();
}
}
private void setInternalMicMute(boolean mute) {
}
}
Updated question! I am using these helper class to detect when a Bluetooth headset is connected, and after that I call startVoiceRecognition() from BluetoothHeadset class.
Android should manage to use headset mic when available does it work using:
setAudioSource(AudioSource.DEFAULT);
More info here.
Hope this helps.

Categories

Resources