diff options
| author | 2025-02-22 23:40:11 +0000 | |
|---|---|---|
| committer | 2025-02-27 02:53:07 +0000 | |
| commit | 6ac41ac32e00992eb329a355155cf93c52b6b9f0 (patch) | |
| tree | 224053956118933485803be13e7e25caca8452ab | |
| parent | afd3be1f827b6db8e90662e32b4ca7695d7c90c8 (diff) | |
AudioDeviceBroker: fix communication route when device is disconnected
When a device selected by a communication route client is disconnected,
do not remove the communication client from the stack, but mark it
as disabled instead.
If a device of the same type is reconnected, re-enable the client.
Bug: 383642693
Test: repro steps in bug
Flag: EXEMPT bug fix
Change-Id: I778d7ac05e67f4d93a3173cadb77e69425b0423d
| -rw-r--r-- | services/core/java/com/android/server/audio/AudioDeviceBroker.java | 177 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/AudioDeviceInventory.java | 29 |
2 files changed, 144 insertions, 62 deletions
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index ef80d59993e9..8ef79a916530 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -16,6 +16,22 @@ package com.android.server.audio; import static android.media.audio.Flags.scoManagedByAudio; +import static android.media.AudioSystem.DEVICE_IN_ALL_SCO_SET; +import static android.media.AudioSystem.DEVICE_IN_BLE_HEADSET; +import static android.media.AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; +import static android.media.AudioSystem.DEVICE_IN_USB_HEADSET; +import static android.media.AudioSystem.DEVICE_IN_WIRED_HEADSET; +import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO_SET; +import static android.media.AudioSystem.DEVICE_OUT_BLE_HEADSET; +import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; +import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT; +import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET; +import static android.media.AudioSystem.DEVICE_OUT_BUS; +import static android.media.AudioSystem.DEVICE_OUT_EARPIECE; +import static android.media.AudioSystem.DEVICE_OUT_SPEAKER; +import static android.media.AudioSystem.DEVICE_OUT_USB_HEADSET; +import static android.media.AudioSystem.DEVICE_OUT_WIRED_HEADSET; +import static android.media.AudioSystem.isBluetoothScoOutDevice; import static com.android.media.audio.Flags.equalScoLeaVcIndexRange; import static com.android.media.audio.Flags.optimizeBtDeviceSwitch; @@ -78,9 +94,11 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; @@ -514,7 +532,7 @@ public class AudioDeviceBroker { @GuardedBy("mDeviceStateLock") private CommunicationRouteClient topCommunicationRouteClient() { for (CommunicationRouteClient crc : mCommunicationRouteClients) { - if (crc.getUid() == mAudioModeOwner.mUid) { + if (crc.getUid() == mAudioModeOwner.mUid && !crc.isDisabled()) { return crc; } } @@ -574,36 +592,6 @@ public class AudioDeviceBroker { return false; } - /*package */ - void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) { - if (!isValidCommunicationDeviceType( - AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) { - return; - } - sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device); - } - - @GuardedBy("mDeviceStateLock") - void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) { - if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString()); - } - for (CommunicationRouteClient crc : mCommunicationRouteClients) { - if (device.equals(crc.getDevice())) { - if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: " - + crc.toString()); - } - // Cancelling the route for this client will remove it from the stack and update - // the communication route. - CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo( - crc.getBinder(), crc.getAttributionSource(), device, false, - BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval", - crc.isPrivileged()); - postSetCommunicationDeviceForClient(deviceInfo); - } - } - } // check playback or record activity after 6 seconds for UIDs private static final int CHECK_CLIENT_STATE_DELAY_MS = 6000; @@ -1693,8 +1681,18 @@ public class AudioDeviceBroker { boolean connect, @Nullable BluetoothDevice btDevice, boolean deviceSwitch) { synchronized (mDeviceStateLock) { - return mDeviceInventory.handleDeviceConnection( + boolean status = mDeviceInventory.handleDeviceConnection( attributes, connect, false /*for test*/, btDevice, deviceSwitch); + if (isValidCommunicationDeviceType(attributes.getType()) + || mDuplexCommunicationDevices.containsValue(attributes.getInternalType())) { + checkCommunicationRouteClientsDevices(); + if (connect || !deviceSwitch) { + onUpdateCommunicationRouteClient( + bluetoothScoRequestOwnerAttributionSource(), + "handleDeviceConnection"); + } + } + return status; } } @@ -1953,15 +1951,17 @@ public class AudioDeviceBroker { || btInfo.mIsLeOutput) ? mAudioService.getBluetoothContextualVolumeStream() : AudioSystem.STREAM_DEFAULT); - if ((btInfo.mProfile == BluetoothProfile.LE_AUDIO + if (btInfo.mProfile == BluetoothProfile.LE_AUDIO || btInfo.mProfile == BluetoothProfile.HEARING_AID || (mScoManagedByAudio - && btInfo.mProfile == BluetoothProfile.HEADSET)) - && (btInfo.mState == BluetoothProfile.STATE_CONNECTED - || !btInfo.mIsDeviceSwitch)) { - onUpdateCommunicationRouteClient( + && btInfo.mProfile == BluetoothProfile.HEADSET)) { + checkCommunicationRouteClientsDevices(); + if (btInfo.mState == BluetoothProfile.STATE_CONNECTED + || !btInfo.mIsDeviceSwitch) { + onUpdateCommunicationRouteClient( bluetoothScoRequestOwnerAttributionSource(), "setBluetoothActiveDevice"); + } } } } @@ -2123,13 +2123,6 @@ public class AudioDeviceBroker { final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; BtHelper.onNotifyPreferredAudioProfileApplied(btDevice); } break; - case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: { - synchronized (mSetModeLock) { - synchronized (mDeviceStateLock) { - onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj); - } - } - } break; case MSG_PERSIST_AUDIO_DEVICE_SETTINGS: onPersistAudioDeviceSettings(); break; @@ -2227,7 +2220,6 @@ public class AudioDeviceBroker { private static final int MSG_IIL_BTLEAUDIO_TIMEOUT = 49; private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52; - private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53; private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54; @@ -2431,18 +2423,21 @@ public class AudioDeviceBroker { private final IBinder mCb; @NonNull private final AttributionSource mAttributionSource; private final boolean mIsPrivileged; - private AudioDeviceAttributes mDevice; + @NonNull private AudioDeviceAttributes mDevice; private boolean mPlaybackActive; private boolean mRecordingActive; + private boolean mDisabled; + CommunicationRouteClient(IBinder cb, @NonNull AttributionSource attributionSource, - AudioDeviceAttributes device, boolean isPrivileged) { + @NonNull AudioDeviceAttributes device, boolean isPrivileged) { mCb = cb; mAttributionSource = attributionSource; mDevice = device; mIsPrivileged = isPrivileged; mPlaybackActive = mAudioService.isPlaybackActiveForUid(attributionSource.getUid()); mRecordingActive = mAudioService.isRecordingActiveForUid(attributionSource.getUid()); + mDisabled = false; } public boolean registerDeathRecipient() { @@ -2485,7 +2480,10 @@ public class AudioDeviceBroker { return mIsPrivileged; } - AudioDeviceAttributes getDevice() { + void setDevice(@NonNull AudioDeviceAttributes device) { + mDevice = device; + } + @NonNull AudioDeviceAttributes getDevice() { return mDevice; } @@ -2498,7 +2496,14 @@ public class AudioDeviceBroker { } public boolean isActive() { - return mIsPrivileged || mRecordingActive || mPlaybackActive; + return !mDisabled && (mIsPrivileged || mRecordingActive || mPlaybackActive); + } + + public void setDisabled(boolean disabled) { + mDisabled = disabled; + } + public boolean isDisabled() { + return mDisabled; } @Override @@ -2507,7 +2512,8 @@ public class AudioDeviceBroker { + " mDevice: " + mDevice.toString() + " mIsPrivileged: " + mIsPrivileged + " mPlaybackActive: " + mPlaybackActive - + " mRecordingActive: " + mRecordingActive + "]"; + + " mRecordingActive: " + mRecordingActive + + " mDisabled: " + mDisabled + "]"; } } @@ -2593,6 +2599,76 @@ public class AudioDeviceBroker { onUpdatePhoneStrategyDevice(preferredCommunicationDevice); } + // Pairs of input and output devices for duplex communication devices (headsets) + private final HashMap<Integer, Integer> mDuplexCommunicationDevices = new HashMap<>( + Map.of(DEVICE_OUT_BLE_HEADSET, DEVICE_IN_BLE_HEADSET, + DEVICE_OUT_WIRED_HEADSET, DEVICE_IN_WIRED_HEADSET, + DEVICE_OUT_USB_HEADSET, DEVICE_IN_USB_HEADSET, + DEVICE_OUT_BLUETOOTH_SCO, DEVICE_IN_BLUETOOTH_SCO_HEADSET, + DEVICE_OUT_BLUETOOTH_SCO_HEADSET, DEVICE_IN_BLUETOOTH_SCO_HEADSET, + DEVICE_OUT_BLUETOOTH_SCO_CARKIT, DEVICE_IN_BLUETOOTH_SCO_HEADSET + )); + /** + * Scan communication route clients and disable them if their selected device is not connected + * or re-enable them if a device of the same type as their connected device is connected + */ + // @GuardedBy("mSetModeLock") + @GuardedBy("mDeviceStateLock") + private void checkCommunicationRouteClientsDevices() { + for (CommunicationRouteClient crc : mCommunicationRouteClients) { + int deviceType = crc.getDevice().getInternalType(); + // Skip non detachable devices + if (deviceType == DEVICE_OUT_EARPIECE || deviceType == DEVICE_OUT_SPEAKER + || deviceType == DEVICE_OUT_BUS) { + continue; + } + + // outDeviceSet is the expected connected output device types for the requested device + Set<Integer> outDeviceSet = null; + // inDeviceSet is the expected input device for outDeviceSet. Null for non + // duplex devices + Set<Integer> inDeviceSet = null; + // Special case for SCO because several device types are equivalent + if (isBluetoothScoOutDevice(deviceType)) { + outDeviceSet = DEVICE_OUT_ALL_SCO_SET; + inDeviceSet = DEVICE_IN_ALL_SCO_SET; + } else { + outDeviceSet = new HashSet<>(); + outDeviceSet.add(deviceType); + if (mDuplexCommunicationDevices.containsKey(deviceType)) { + inDeviceSet = new HashSet<>(); + inDeviceSet.add(mDuplexCommunicationDevices.get(deviceType)); + } + } + + AudioDeviceAttributes outAda = + mDeviceInventory.getFirstConnectedDeviceAttributesOfTypes(outDeviceSet); + AudioDeviceAttributes inAda = (inDeviceSet == null) ? null + : mDeviceInventory.getFirstConnectedDeviceAttributesOfTypes(inDeviceSet); + + // A device is fully connected if the output device is connect and if not duplex + // or an input device with the same address is connected + boolean fullyConnected = outAda != null && (inDeviceSet == null + || (inAda != null && inAda.getAddress().equals(outAda.getAddress()))); + + if (fullyConnected) { + crc.setDevice(outAda); + if (crc.isDisabled()) { + crc.setDisabled(false); + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, + "checkCommunicationRouteClientsDevices, enabling client: " + crc); + } + } + } else if (!crc.isDisabled()) { + crc.setDisabled(true); + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "checkCommunicationRouteClientsDevices, disabling client: " + crc); + } + } + } + } + /** * Select new communication device from communication route client at the top of the stack * and restore communication route including restarting SCO audio if needed. @@ -2601,6 +2677,7 @@ public class AudioDeviceBroker { @GuardedBy("mDeviceStateLock") private void onUpdateCommunicationRouteClient( @Nullable AttributionSource previousBtScoRequesterAS, String eventSource) { + CommunicationRouteClient crc = topCommunicationRouteClient(); if (AudioService.DEBUG_COMM_RTE) { Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index ae91934e7498..829d9ea7495f 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1867,7 +1867,6 @@ public class AudioDeviceInventory { deviceSwitch); // always remove even if disconnection failed mConnectedDevices.remove(deviceKey); - mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes); status = true; } if (status) { @@ -2413,7 +2412,7 @@ public class AudioDeviceInventory { } else { AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( "A2DP device addr=" + Utils.anonymizeBluetoothAddress(address) - + " made unavailable, deviceSwitch" + deviceSwitch)) + + " made unavailable, deviceSwitch: " + deviceSwitch)) .printSlog(EventLogger.Event.ALOGI, TAG)); } mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); @@ -2423,7 +2422,6 @@ public class AudioDeviceInventory { mmi.record(); updateBluetoothPreferredModes_l(null /*connectedDevice*/); purgeDevicesRoles_l(); - mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } @GuardedBy("mDevicesLock") @@ -2479,7 +2477,6 @@ public class AudioDeviceInventory { // always remove regardless of the result mConnectedDevices.remove( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); - mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } @GuardedBy("mDevicesLock") @@ -2541,7 +2538,6 @@ public class AudioDeviceInventory { .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID)) .record(); - mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } @GuardedBy("mDevicesLock") @@ -2572,6 +2568,15 @@ public class AudioDeviceInventory { } /** + * Returns a DeviceInfo for the first connected device matching one of the supplied types + */ + AudioDeviceAttributes getFirstConnectedDeviceAttributesOfTypes(Set<Integer> internalTypes) { + DeviceInfo di = getFirstConnectedDeviceOfTypes(internalTypes); + return di == null ? null : new AudioDeviceAttributes( + di.mDeviceType, di.mDeviceAddress, di.mDeviceName); + } + + /** * Returns a list of connected devices matching one of the supplied types */ private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) { @@ -2689,13 +2694,16 @@ public class AudioDeviceInventory { if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "APM failed to make unavailable LE Audio device addr=" + address - + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG)); + "APM failed to make unavailable LE Audio " + + (AudioSystem.isInputDevice(device) ? "source" : "sink") + + " device addr=" + address + + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG)); // not taking further action: proceeding as if disconnection from APM worked } else { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address) - + " made unavailable, deviceSwitch" + deviceSwitch) + "LE Audio " + (AudioSystem.isInputDevice(device) ? "source" : "sink") + + "device addr=" + Utils.anonymizeBluetoothAddress(address) + + " made unavailable, deviceSwitch: " + deviceSwitch) .printSlog(EventLogger.Event.ALOGI, TAG)); } mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); @@ -2704,9 +2712,6 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); updateBluetoothPreferredModes_l(null /*connectedDevice*/); purgeDevicesRoles_l(); - if (ada != null) { - mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); - } } @GuardedBy("mDevicesLock") |