Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 57a046e7 authored by Eric Laurent's avatar Eric Laurent
Browse files

AudioService: refactor BT profile management

Remove intent BluetoothAdapter.ACTION_STATE_CHANGED listener in AudioService
and only manage BT profiles disconnection via the profile service listener in BtHelper.
This removes possible race conditions between a disconnection received
via the intent and a connection via the service listener.
Also align BT Headset profile management on A2DP and LE Audio profile
management in AudioDeviceBroker, AudioDeviceInventory and BtHelper.
Process all profile related actions in AudioDeviceBroker message handler
for proper serialization.

Bug: 289681953
Test: make
Change-Id: Ie2b271a846455620fad9bbc519dd8b1876d78889
Merged-In: I7106b7516706c542be1fbdad65067b3b09de6a61
parent b712a758
Loading
Loading
Loading
Loading
+17 −33
Original line number Diff line number Diff line
@@ -257,24 +257,19 @@ public class AudioDeviceBroker {
        sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
    }

    /*package*/ void disconnectAllBluetoothProfiles() {
        synchronized (mDeviceStateLock) {
            mBtHelper.disconnectAllBluetoothProfiles();
        }
    }

    /**
     * Handle BluetoothHeadset intents where the action is one of
     *   {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
     *   {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
     * @param intent
     */
    /*package*/ void receiveBtEvent(@NonNull Intent intent) {
        synchronized (mSetModeLock) {
            synchronized (mDeviceStateLock) {
                mBtHelper.receiveBtEvent(intent);
            }
    private void onReceiveBtEvent(@NonNull Intent intent) {
        mBtHelper.onReceiveBtEvent(intent);
    }

    @GuardedBy("mDeviceStateLock")
    /*package*/ void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
        mBtHelper.onSetBtScoActiveDevice(btDevice);
    }

    /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
@@ -1404,14 +1399,14 @@ public class AudioDeviceBroker {
        sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT, SENDMSG_QUEUE, info);
    }

    /*package*/ void postScoAudioStateChanged(int state) {
        sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
    }

    /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
        sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice);
    }

    /*package*/ void postReceiveBtEvent(Intent intent) {
        sendLMsgNoDelay(MSG_L_RECEIVED_BT_EVENT, SENDMSG_QUEUE, intent);
    }

    /*package*/ static final class CommunicationDeviceInfo {
        final @NonNull IBinder mCb; // Identifies the requesting client for death handler
        final int mUid; // Requester UID
@@ -1807,10 +1802,10 @@ public class AudioDeviceBroker {
                    }
                    break;

                case MSG_I_SCO_AUDIO_STATE_CHANGED:
                case MSG_L_RECEIVED_BT_EVENT:
                    synchronized (mSetModeLock) {
                        synchronized (mDeviceStateLock) {
                            mBtHelper.onScoAudioStateChanged(msg.arg1);
                            onReceiveBtEvent((Intent) msg.obj);
                        }
                    }
                    break;
@@ -1821,29 +1816,17 @@ public class AudioDeviceBroker {
                    }
                    break;
                case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE:
                    if (msg.arg1 != BluetoothProfile.HEADSET) {
                    synchronized (mSetModeLock) {
                        synchronized (mDeviceStateLock) {
                            mBtHelper.onBtProfileDisconnected(msg.arg1);
                            mDeviceInventory.onBtProfileDisconnected(msg.arg1);
                        }
                    } else {
                        synchronized (mSetModeLock) {
                            synchronized (mDeviceStateLock) {
                                mBtHelper.disconnectHeadset();
                            }
                        }
                    }
                    break;
                case MSG_IL_BT_SERVICE_CONNECTED_PROFILE:
                    if (msg.arg1 != BluetoothProfile.HEADSET) {
                        synchronized (mDeviceStateLock) {
                            mBtHelper.onBtProfileConnected(msg.arg1, (BluetoothProfile) msg.obj);
                        }
                    } else {
                    synchronized (mSetModeLock) {
                        synchronized (mDeviceStateLock) {
                                mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
                            }
                            mBtHelper.onBtProfileConnected(msg.arg1, (BluetoothProfile) msg.obj);
                        }
                    }
                    break;
@@ -1978,7 +1961,6 @@ public class AudioDeviceBroker {

    private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
    private static final int MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
    private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;

    private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
    //
@@ -1994,6 +1976,8 @@ public class AudioDeviceBroker {

    private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54;

    private static final int MSG_L_RECEIVED_BT_EVENT = 55;

    private static boolean isMessageHandledUnderWakelock(int msgId) {
        switch(msgId) {
            case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+23 −1
Original line number Diff line number Diff line
@@ -1489,8 +1489,12 @@ public class AudioDeviceInventory {
        }
    }

    /*package*/ synchronized void onBtProfileDisconnected(int profile) {
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ void onBtProfileDisconnected(int profile) {
        switch (profile) {
            case BluetoothProfile.HEADSET:
                disconnectHeadset();
                break;
            case BluetoothProfile.A2DP:
                disconnectA2dp();
                break;
@@ -1550,6 +1554,24 @@ public class AudioDeviceInventory {
        disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
    }

    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    private void disconnectHeadset() {
        boolean disconnect = false;
        synchronized (mDevicesLock) {
            for (DeviceInfo di : mConnectedDevices.values()) {
                if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
                    // There is only one HFP active device and setting the active
                    // device to null will disconnect both in and out devices
                    disconnect = true;
                    break;
                }
            }
        }
        if (disconnect) {
            mDeviceBroker.onSetBtScoActiveDevice(null);
        }
    }

    // must be called before removing the device from mConnectedDevices
    // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
    // from AudioSystem
+1 −12
Original line number Diff line number Diff line
@@ -61,7 +61,6 @@ import android.app.NotificationManager;
import android.app.UidObserver;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
@@ -1366,7 +1365,6 @@ public class AudioService extends IAudioService.Stub
        intentFilter.addAction(Intent.ACTION_USER_BACKGROUND);
        intentFilter.addAction(Intent.ACTION_USER_FOREGROUND);
        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        intentFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
        intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
@@ -9570,7 +9568,7 @@ public class AudioService extends IAudioService.Stub
                mDockState = dockState;
            } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)
                    || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                mDeviceBroker.receiveBtEvent(intent);
                mDeviceBroker.postReceiveBtEvent(intent);
            } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                if (mMonitorRotation) {
                    RotationHelper.enable();
@@ -9638,15 +9636,6 @@ public class AudioService extends IAudioService.Stub
                } catch (IllegalArgumentException e) {
                    Slog.w(TAG, "Failed to apply DISALLOW_RECORD_AUDIO restriction: " + e);
                }
            } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
                sDeviceLogger.enqueue(new EventLogger.StringEvent(
                        "BluetoothAdapter ACTION_STATE_CHANGED with state " + state));
                if (state == BluetoothAdapter.STATE_OFF ||
                        state == BluetoothAdapter.STATE_TURNING_OFF) {
                    mDeviceBroker.disconnectAllBluetoothProfiles();
                }
            } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) ||
                    action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
                handleAudioEffectBroadcast(context, intent);
+52 −56
Original line number Diff line number Diff line
@@ -191,10 +191,14 @@ public class BtHelper {
        if (adapter != null) {
            adapter.getProfileProxy(mDeviceBroker.getContext(),
                    mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
            adapter.getProfileProxy(mDeviceBroker.getContext(),
                    mBluetoothProfileServiceListener, BluetoothProfile.A2DP_SINK);
            adapter.getProfileProxy(mDeviceBroker.getContext(),
                    mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
            adapter.getProfileProxy(mDeviceBroker.getContext(),
                    mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO);
            adapter.getProfileProxy(mDeviceBroker.getContext(),
                    mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
        }
    }

@@ -261,27 +265,27 @@ public class BtHelper {

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void receiveBtEvent(Intent intent) {
    /*package*/ synchronized void onReceiveBtEvent(Intent intent) {
        final String action = intent.getAction();

        Log.i(TAG, "receiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState);
        Log.i(TAG, "onReceiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState);
        if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, android.bluetooth.BluetoothDevice.class);
            setBtScoActiveDevice(btDevice);
            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE,
                    android.bluetooth.BluetoothDevice.class);
            onSetBtScoActiveDevice(btDevice);
        } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
            int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
            Log.i(TAG,"receiveBtEvent ACTION_AUDIO_STATE_CHANGED: "+btState);
            mDeviceBroker.postScoAudioStateChanged(btState);
            onScoAudioStateChanged(btState);
        }
    }

    /**
     * Exclusively called from AudioDeviceBroker when handling MSG_I_SCO_AUDIO_STATE_CHANGED
     * Exclusively called from AudioDeviceBroker when handling MSG_L_RECEIVED_BT_EVENT
     * as part of the serialization of the communication route selection
     */
    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    void onScoAudioStateChanged(int state) {
    private void onScoAudioStateChanged(int state) {
        boolean broadcast = false;
        int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
        switch (state) {
@@ -294,10 +298,10 @@ public class BtHelper {
                    // broadcast intent if the connection was initated by AudioService
                    broadcast = true;
                }
                mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
                mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
                break;
            case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
                mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
                mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
                scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
                // There are two cases where we want to immediately reconnect audio:
                // 1) If a new start request was received while disconnecting: this was
@@ -431,15 +435,6 @@ public class BtHelper {
        mScoConnectionState = state;
    }

    /*package*/ synchronized void disconnectAllBluetoothProfiles() {
        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.A2DP);
        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.A2DP_SINK);
        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEADSET);
        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEARING_AID);
        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO);
        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO_BROADCAST);
    }

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void resetBluetoothSco() {
@@ -450,18 +445,14 @@ public class BtHelper {
        mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
    }

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void disconnectHeadset() {
        setBtScoActiveDevice(null);
        mBluetoothHeadset = null;
    }

    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void onBtProfileDisconnected(int profile) {
        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                "BT profile " + BluetoothProfile.getProfileName(profile) + " disconnected"));
        switch (profile) {
            case BluetoothProfile.HEADSET:
                mBluetoothHeadset = null;
                break;
            case BluetoothProfile.A2DP:
                mA2dp = null;
                break;
@@ -471,14 +462,10 @@ public class BtHelper {
            case BluetoothProfile.LE_AUDIO:
                mLeAudio = null;
                break;

            case BluetoothProfile.A2DP_SINK:
            case BluetoothProfile.LE_AUDIO_BROADCAST:
                // shouldn't be received here as profile doesn't involve BtHelper
                Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper "
                        + BluetoothProfile.getProfileName(profile));
                // nothing to do in BtHelper
                break;

            default:
                // Not a valid profile to disconnect
                Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect "
@@ -492,17 +479,31 @@ public class BtHelper {
        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
                + proxy));
        if (profile == BluetoothProfile.HEADSET) {
        switch (profile) {
            case BluetoothProfile.HEADSET:
                onHeadsetProfileConnected((BluetoothHeadset) proxy);
                return;
        }
        if (profile == BluetoothProfile.A2DP) {
            case BluetoothProfile.A2DP:
                mA2dp = (BluetoothA2dp) proxy;
        } else if (profile == BluetoothProfile.HEARING_AID) {
                break;
            case BluetoothProfile.HEARING_AID:
                mHearingAid = (BluetoothHearingAid) proxy;
        } else if (profile == BluetoothProfile.LE_AUDIO) {
                break;
            case BluetoothProfile.LE_AUDIO:
                mLeAudio = (BluetoothLeAudio) proxy;
                break;
            case BluetoothProfile.A2DP_SINK:
            case BluetoothProfile.LE_AUDIO_BROADCAST:
                // nothing to do in BtHelper
                return;
            default:
                // Not a valid profile to connect
                Log.e(TAG, "onBtProfileConnected: Not a valid profile to connect "
                        + BluetoothProfile.getProfileName(profile));
                break;
        }

        // this part is only for A2DP, LE Audio unicast and Hearing aid
        final List<BluetoothDevice> deviceList = proxy.getConnectedDevices();
        if (deviceList.isEmpty()) {
            return;
@@ -523,7 +524,7 @@ public class BtHelper {

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
    private void onHeadsetProfileConnected(BluetoothHeadset headset) {
        // Discard timeout message
        mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
        mBluetoothHeadset = headset;
@@ -532,7 +533,7 @@ public class BtHelper {
        if (adapter != null) {
            activeDevices = adapter.getActiveDevices(BluetoothProfile.HEADSET);
        }
        setBtScoActiveDevice((activeDevices.size() > 0) ? activeDevices.get(0) : null);
        onSetBtScoActiveDevice((activeDevices.size() > 0) ? activeDevices.get(0) : null);
        // Refresh SCO audio state
        checkScoAudioState();
        if (mScoAudioState != SCO_STATE_ACTIVATE_REQ
@@ -643,20 +644,19 @@ public class BtHelper {

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    @GuardedBy("BtHelper.this")
    private void setBtScoActiveDevice(BluetoothDevice btDevice) {
        Log.i(TAG, "setBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
    /*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
        Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
                + " -> " + getAnonymizedAddress(btDevice));
        final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
        if (Objects.equals(btDevice, previousActiveDevice)) {
            return;
        }
        if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
            Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
            Log.w(TAG, "onSetBtScoActiveDevice() failed to remove previous device "
                    + getAnonymizedAddress(previousActiveDevice));
        }
        if (!handleBtScoActiveDeviceChange(btDevice, true)) {
            Log.e(TAG, "setBtScoActiveDevice() failed to add new device "
            Log.e(TAG, "onSetBtScoActiveDevice() failed to add new device "
                    + getAnonymizedAddress(btDevice));
            // set mBluetoothHeadsetDevice to null when failing to add new device
            btDevice = null;
@@ -677,16 +677,14 @@ public class BtHelper {
                        case BluetoothProfile.HEADSET:
                        case BluetoothProfile.HEARING_AID:
                        case BluetoothProfile.LE_AUDIO:
                        case BluetoothProfile.A2DP_SINK:
                        case BluetoothProfile.LE_AUDIO_BROADCAST:
                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                                    "BT profile service: connecting "
                                    + BluetoothProfile.getProfileName(profile) + " profile"));
                            mDeviceBroker.postBtProfileConnected(profile, proxy);
                            break;

                        case BluetoothProfile.A2DP_SINK:
                            // no A2DP sink functionality handled by BtHelper
                        case BluetoothProfile.LE_AUDIO_BROADCAST:
                            // no broadcast functionality handled by BtHelper
                        default:
                            break;
                    }
@@ -698,16 +696,14 @@ public class BtHelper {
                        case BluetoothProfile.HEADSET:
                        case BluetoothProfile.HEARING_AID:
                        case BluetoothProfile.LE_AUDIO:
                        case BluetoothProfile.A2DP_SINK:
                        case BluetoothProfile.LE_AUDIO_BROADCAST:
                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                                    "BT profile service: disconnecting "
                                        + BluetoothProfile.getProfileName(profile) + " profile"));
                            mDeviceBroker.postBtProfileDisconnected(profile);
                            break;

                        case BluetoothProfile.A2DP_SINK:
                            // no A2DP sink functionality handled by BtHelper
                        case BluetoothProfile.LE_AUDIO_BROADCAST:
                            // no broadcast functionality handled by BtHelper
                        default:
                            break;
                    }