BT-HFP: Update Bluetooth headset state handler to Multi-HFP

* When multiple headset devices are connected at the same time, at most one
  device can be used for SCO audio at any time. This device is called
  Active Device and is indicated by either
  BluetoothHeadset.getActiveDevice() or
  BluetoothHeadset.ACTIVE_DEVICE_CHANGED intent. It can also be set
  through BluetoothHeadset.setActiveDevice(BluetoothDevice) internal API.
* This change let AudioService to listen to ACTIVE_DEVICE_CHANGED intent
  instead of CONNECTION_STATE_CHANGED intent since it is the active
  device that AudioService cares about, not the list of connected
  devices.
* Everytime a new active device is set, AudioService will treat the old
  one (if not null) as disconnected and call disconnection methods in
  audio framework and the new active device is regarded as newly
  connected and connection methods will be called by AudioService.
* When disconnectHeadset() is called, active device will be set to null

Bug: 71875419
Test: compile, connect multiple HFP devices and switch active device
      among them
Change-Id: I148cca079d36a2dfc6a46b8d42ba69821c9c6de3
This commit is contained in:
Jack He
2018-01-17 15:45:12 -08:00
committed by Pavlin Radoslavov
parent c584d2772d
commit 8dd33941a4

View File

@@ -136,7 +136,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -768,7 +767,7 @@ public class AudioService extends IAudioService.Stub
// Register for device connection intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
@@ -2929,14 +2928,28 @@ public class AudioService extends IAudioService.Stub
}
public void setBluetoothScoOnInt(boolean on, String eventSource) {
if (DEBUG_DEVICES) {
Log.d(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
}
if (on) {
// do not accept SCO ON if SCO audio is not connected
synchronized(mScoClients) {
if ((mBluetoothHeadset != null) &&
(mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
!= BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
return;
synchronized (mScoClients) {
if (mBluetoothHeadset != null) {
if (mBluetoothHeadsetDevice == null) {
BluetoothDevice activeDevice = mBluetoothHeadset.getActiveDevice();
if (activeDevice != null) {
// setBtScoActiveDevice() might trigger resetBluetoothSco() which
// will call setBluetoothScoOnInt(false, "resetBluetoothSco")
setBtScoActiveDevice(activeDevice);
}
}
if (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
!= BluetoothHeadset.STATE_AUDIO_CONNECTED) {
mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
+ mBluetoothHeadsetDevice + " is not in audio connected mode");
return;
}
}
}
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
@@ -3324,24 +3337,23 @@ public class AudioService extends IAudioService.Stub
}
}
void setBtScoDeviceConnectionState(BluetoothDevice btDevice, int state) {
private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
if (btDevice == null) {
return;
return true;
}
String address = btDevice.getAddress();
BluetoothClass btClass = btDevice.getBluetoothClass();
int outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
if (btClass != null) {
switch (btClass.getDeviceClass()) {
case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
break;
case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
break;
case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
break;
case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
break;
}
}
@@ -3349,34 +3361,33 @@ public class AudioService extends IAudioService.Stub
address = "";
}
boolean connected = (state == BluetoothProfile.STATE_CONNECTED);
String btDeviceName = btDevice.getName();
boolean success =
handleDeviceConnection(connected, outDevice, address, btDeviceName) &&
handleDeviceConnection(connected, inDevice, address, btDeviceName);
boolean result = handleDeviceConnection(isActive, outDevice, address, btDeviceName);
// handleDeviceConnection() && result to make sure the method get executed
result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result;
return result;
}
if (!success) {
return;
void setBtScoActiveDevice(BluetoothDevice btDevice) {
if (DEBUG_DEVICES) {
Log.d(TAG, "setBtScoActiveDevice(" + btDevice + ")");
}
/* When one BT headset is disconnected while another BT headset
* is connected, don't mess with the headset device.
*/
if ((state == BluetoothProfile.STATE_DISCONNECTED ||
state == BluetoothProfile.STATE_DISCONNECTING) &&
mBluetoothHeadset != null &&
mBluetoothHeadset.getAudioState(btDevice) == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
Log.w(TAG, "SCO connected through another device, returning");
return;
}
synchronized (mScoClients) {
if (connected) {
final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
if (!Objects.equals(btDevice, previousActiveDevice)) {
if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+ previousActiveDevice);
}
if (!handleBtScoActiveDeviceChange(btDevice, true)) {
Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
// set mBluetoothHeadsetDevice to null when failing to add new device
btDevice = null;
}
mBluetoothHeadsetDevice = btDevice;
} else {
mBluetoothHeadsetDevice = null;
resetBluetoothSco();
if (mBluetoothHeadsetDevice == null) {
resetBluetoothSco();
}
}
}
}
@@ -3431,12 +3442,7 @@ public class AudioService extends IAudioService.Stub
// Discard timeout message
mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
mBluetoothHeadset = (BluetoothHeadset) proxy;
deviceList = mBluetoothHeadset.getConnectedDevices();
if (deviceList.size() > 0) {
mBluetoothHeadsetDevice = deviceList.get(0);
} else {
mBluetoothHeadsetDevice = null;
}
setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
// Refresh SCO audio state
checkScoAudioState();
// Continue pending action if any
@@ -3557,10 +3563,7 @@ public class AudioService extends IAudioService.Stub
void disconnectHeadset() {
synchronized (mScoClients) {
if (mBluetoothHeadsetDevice != null) {
setBtScoDeviceConnectionState(mBluetoothHeadsetDevice,
BluetoothProfile.STATE_DISCONNECTED);
}
setBtScoActiveDevice(null);
mBluetoothHeadset = null;
}
}
@@ -5732,11 +5735,9 @@ public class AudioService extends IAudioService.Stub
AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
}
mDockState = dockState;
} else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
BluetoothProfile.STATE_DISCONNECTED);
} else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
setBtScoDeviceConnectionState(btDevice, state);
setBtScoActiveDevice(btDevice);
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;