summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Eric Laurent <elaurent@google.com> 2025-02-22 23:40:11 +0000
committer Eric Laurent <elaurent@google.com> 2025-02-27 02:53:07 +0000
commit6ac41ac32e00992eb329a355155cf93c52b6b9f0 (patch)
tree224053956118933485803be13e7e25caca8452ab
parentafd3be1f827b6db8e90662e32b4ca7695d7c90c8 (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.java177
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java29
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")