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

Commit a84c0968 authored by Cheney Ni's avatar Cheney Ni
Browse files

Reduced the audio effort while changing the A2DP active device

* Functions to change the active device must be exclusive of each other,
  and is separated from the mStateMachine synchronized.
* Move the mAudioManager calls out of the mStateMachine synchronized,
  and it breaks the deadlock even if the AudioManager invokes A2DP APIs
  in the same time.

Bug: 132035154
Bug: 128962562
Test: TH and switch A2DP device manually
Change-Id: Ib55f5dc51dfe0354b106bdd5bb7cee59c9e149e1
parent 87aa31a3
Loading
Loading
Loading
Loading
+113 −96
Original line number Diff line number Diff line
@@ -72,6 +72,9 @@ public class A2dpService extends ProfileService {
    private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines =
            new ConcurrentHashMap<>();

    // Protect setActiveDevice() so all invoked is handled squentially
    private final Object mActiveSwitchingGuard = new Object();

    // Upper limit of all A2DP devices: Bonded or Connected
    private static final int MAX_A2DP_STATE_MACHINES = 50;
    // Upper limit of all A2DP devices that are Connected or Connecting
@@ -434,16 +437,16 @@ public class A2dpService extends ProfileService {
    }

    private void removeActiveDevice(boolean forceStopPlayingAudio) {
        BluetoothDevice previousActiveDevice = mActiveDevice;
        synchronized (mActiveSwitchingGuard) {
            BluetoothDevice previousActiveDevice = null;
            synchronized (mStateMachines) {
                if (mActiveDevice == null) return;
                previousActiveDevice = mActiveDevice;
            }
            // This needs to happen before we inform the audio manager that the device
            // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
            updateAndBroadcastActiveDevice(null);

            if (previousActiveDevice == null) {
                return;
            }

            // Make sure the Audio Manager knows the previous Active device is disconnected.
            // However, if A2DP is still connected and not forcing stop audio for that remote
            // device, the user has explicitly switched the output to the local device and music
@@ -456,6 +459,8 @@ public class A2dpService extends ProfileService {
            mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
                    previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
                    BluetoothProfile.A2DP, suppressNoisyIntent, -1);

            synchronized (mStateMachines) {
                // Make sure the Active device in native layer is set to null and audio is off
                if (!mA2dpNativeInterface.setActiveDevice(null)) {
                    Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
@@ -463,6 +468,7 @@ public class A2dpService extends ProfileService {
                }
            }
        }
    }

    /**
     * Process a change in the silence mode for a {@link BluetoothDevice}.
@@ -497,20 +503,26 @@ public class A2dpService extends ProfileService {
     */
    public boolean setActiveDevice(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
        synchronized (mStateMachines) {
            BluetoothDevice previousActiveDevice = mActiveDevice;
            if (DBG) {
                Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
            }

        synchronized (mActiveSwitchingGuard) {
            if (device == null) {
                // Remove active device and continue playing audio only if necessary.
                removeActiveDevice(false);
                return true;
            }

            BluetoothCodecStatus codecStatus = null;
            A2dpStateMachine sm = mStateMachines.get(device);
            A2dpStateMachine sm = null;
            BluetoothDevice previousActiveDevice = null;
            synchronized (mStateMachines) {
                if (Objects.equals(device, mActiveDevice)) {
                    Log.i(TAG, "setActiveDevice(" + device + "): current is " + mActiveDevice
                            + " no changed");
                    // returns true since the device is activated even double attempted
                    return true;
                }
                if (DBG) {
                    Log.d(TAG, "setActiveDevice(" + device + "): current is " + mActiveDevice);
                }
                sm = mStateMachines.get(device);
                if (sm == null) {
                    Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
                              + "no state machine");
@@ -521,21 +533,16 @@ public class A2dpService extends ProfileService {
                              + "device is not connected");
                    return false;
                }
            if (!mA2dpNativeInterface.setActiveDevice(device)) {
                Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
                return false;
                previousActiveDevice = mActiveDevice;
            }
            codecStatus = sm.getCodecStatus();

            boolean deviceChanged = !Objects.equals(device, mActiveDevice);
            // Switch from one A2DP to another A2DP device
            if (DBG) {
                Log.d(TAG, "Switch A2DP devices to " + device + " from " + previousActiveDevice);
            }
            // This needs to happen before we inform the audio manager that the device
            // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
            updateAndBroadcastActiveDevice(device);
            if (deviceChanged) {
                // Send an intent with the active device codec config
                if (codecStatus != null) {
                    broadcastCodecConfig(mActiveDevice, codecStatus);
                }
            // Make sure the Audio Manager knows the previous Active device is disconnected,
            // and the new Active device is connected.
            // Also, mute and unmute the output during the switch to avoid audio glitches.
@@ -551,26 +558,41 @@ public class A2dpService extends ProfileService {
                        BluetoothProfile.A2DP, true, -1);
            }

            BluetoothDevice newActiveDevice = null;
            synchronized (mStateMachines) {
                if (!mA2dpNativeInterface.setActiveDevice(device)) {
                    Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native "
                            + "layer");
                    // Remove active device and stop playing audio.
                    removeActiveDevice(true);
                    return false;
                }
                // Send an intent with the active device codec config
                BluetoothCodecStatus codecStatus = sm.getCodecStatus();
                if (codecStatus != null) {
                    broadcastCodecConfig(mActiveDevice, codecStatus);
                }
                newActiveDevice = mActiveDevice;
            }

            // Tasks of Bluetooth are done, and now restore the AudioManager side.
            int rememberedVolume = -1;
            if (mFactory.getAvrcpTargetService() != null) {
                rememberedVolume = mFactory.getAvrcpTargetService()
                            .getRememberedVolumeForDevice(mActiveDevice);
                        .getRememberedVolumeForDevice(newActiveDevice);
            }

            mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
                        mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
                    newActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
                    true, rememberedVolume);

            // Inform the Audio Service about the codec configuration
            // change, so the Audio Service can reset accordingly the audio
            // feeding parameters in the Audio HAL to the Bluetooth stack.
                mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
            mAudioManager.handleBluetoothA2dpDeviceConfigChange(newActiveDevice);
            if (wasMuted) {
                mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                        AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
            }
        }
        }
        return true;
    }

@@ -895,11 +917,11 @@ public class A2dpService extends ProfileService {
            Log.d(TAG, "updateAndBroadcastActiveDevice(" + device + ")");
        }

        synchronized (mStateMachines) {
        // Make sure volume has been store before device been remove from active.
        if (mFactory.getAvrcpTargetService() != null) {
            mFactory.getAvrcpTargetService().volumeDeviceSwitched(device);
        }

        synchronized (mStateMachines) {
            mActiveDevice = device;
        }

@@ -964,13 +986,12 @@ public class A2dpService extends ProfileService {
            if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
                return;
            }
        }
        if (mFactory.getAvrcpTargetService() != null) {
            mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
        }

        removeStateMachine(device);
    }
    }

    private void removeStateMachine(BluetoothDevice device) {
        synchronized (mStateMachines) {
@@ -1049,7 +1070,6 @@ public class A2dpService extends ProfileService {
        if ((device == null) || (fromState == toState)) {
            return;
        }
        synchronized (mStateMachines) {
        if (toState == BluetoothProfile.STATE_CONNECTED) {
            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
        }
@@ -1063,17 +1083,14 @@ public class A2dpService extends ProfileService {
        }
        // Check if the device is disconnected - if unbond, remove the state machine
        if (toState == BluetoothProfile.STATE_DISCONNECTED) {
                int bondState = mAdapterService.getBondState(device);
                if (bondState == BluetoothDevice.BOND_NONE) {
            if (mAdapterService.getBondState(device) == BluetoothDevice.BOND_NONE) {
                if (mFactory.getAvrcpTargetService() != null) {
                    mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
                }

                removeStateMachine(device);
            }
        }
    }
    }

    /**
     * Receiver for processing device connection state changes.