From 8dd33941a455bab025beaa549b78c094e81226e2 Mon Sep 17 00:00:00 2001 From: Jack He Date: Wed, 17 Jan 2018 15:45:12 -0800 Subject: [PATCH] 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 --- .../android/server/audio/AudioService.java | 111 +++++++++--------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index f54b243038f22..4293d4510ede6 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -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;