diff options
6 files changed, 334 insertions, 97 deletions
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 28970750a5ac..5d4f711b9432 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -70,7 +70,6 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; -import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -172,7 +171,7 @@ public class AudioDeviceBroker { @NonNull AudioSystemAdapter audioSystem) { mContext = context; mAudioService = service; - mBtHelper = new BtHelper(this); + mBtHelper = new BtHelper(this, context); mDeviceInventory = new AudioDeviceInventory(this); mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext); mAudioSystem = audioSystem; @@ -188,7 +187,7 @@ public class AudioDeviceBroker { @NonNull AudioSystemAdapter audioSystem) { mContext = context; mAudioService = service; - mBtHelper = new BtHelper(this); + mBtHelper = new BtHelper(this, context); mDeviceInventory = mockDeviceInventory; mSystemServer = mockSystemServer; mAudioSystem = audioSystem; @@ -1392,6 +1391,10 @@ public class AudioDeviceBroker { return mAudioService.hasAudioFocusUsers(); } + /*package*/ void postInitSpatializerHeadTrackingSensors() { + mAudioService.postInitSpatializerHeadTrackingSensors(); + } + //--------------------------------------------------------------------- // Message handling on behalf of helper classes. // Each of these methods posts a message to mBrokerHandler message queue. @@ -1475,6 +1478,15 @@ public class AudioDeviceBroker { sendLMsgNoDelay(MSG_L_RECEIVED_BT_EVENT, SENDMSG_QUEUE, intent); } + /*package*/ void postUpdateLeAudioGroupAddresses(int groupId) { + sendIMsgNoDelay( + MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES, SENDMSG_QUEUE, groupId); + } + + /*package*/ void postSynchronizeLeDevicesInInventory(AdiDeviceState deviceState) { + sendLMsgNoDelay(MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState); + } + /*package*/ static final class CommunicationDeviceInfo { final @NonNull IBinder mCb; // Identifies the requesting client for death handler final int mUid; // Requester UID @@ -1604,6 +1616,14 @@ public class AudioDeviceBroker { } } + /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) { + return mBtHelper.getLeAudioDeviceGroupId(device); + } + + /*package*/ List<String> getLeAudioGroupAddresses(int groupId) { + return mBtHelper.getLeAudioGroupAddresses(groupId); + } + /*package*/ void broadcastStickyIntentToCurrentProfileGroup(Intent intent) { mSystemServer.broadcastStickyIntentToCurrentProfileGroup(intent); } @@ -1976,6 +1996,22 @@ public class AudioDeviceBroker { onCheckCommunicationRouteClientState(msg.arg1, msg.arg2 == 1); } } break; + + case MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES: + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + mDeviceInventory.onUpdateLeAudioGroupAddresses(msg.arg1); + } + } break; + + case MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY: + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + mDeviceInventory.onSynchronizeLeDevicesInInventory( + (AdiDeviceState) msg.obj); + } + } break; + default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -2058,6 +2094,10 @@ public class AudioDeviceBroker { private static final int MSG_L_RECEIVED_BT_EVENT = 55; private static final int MSG_CHECK_COMMUNICATION_ROUTE_CLIENT_STATE = 56; + private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57; + private static final int MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY = 58; + + private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { @@ -2582,9 +2622,9 @@ public class AudioDeviceBroker { } } - @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) { + List<String> getDeviceAddresses(AudioDeviceAttributes device) { synchronized (mDeviceStateLock) { - return mDeviceInventory.getDeviceSensorUuid(device); + return mDeviceInventory.getDeviceAddresses(device); } } @@ -2605,15 +2645,19 @@ public class AudioDeviceBroker { * in order to be mocked by a test a the same package * (see https://code.google.com/archive/p/mockito/issues/127) */ - public void persistAudioDeviceSettings() { + public void postPersistAudioDeviceSettings() { sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000); } void onPersistAudioDeviceSettings() { final String deviceSettings = mDeviceInventory.getDeviceSettings(); - Log.v(TAG, "saving AdiDeviceState: " + deviceSettings); - final SettingsAdapter settings = mAudioService.getSettings(); - boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(), + Log.v(TAG, "onPersistAudioDeviceSettings AdiDeviceState: " + deviceSettings); + String currentSettings = readDeviceSettings(); + if (deviceSettings.equals(currentSettings)) { + return; + } + final SettingsAdapter settingsAdapter = mAudioService.getSettings(); + boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(), Settings.Secure.AUDIO_DEVICE_INVENTORY, deviceSettings, UserHandle.USER_CURRENT); if (!res) { @@ -2621,11 +2665,17 @@ public class AudioDeviceBroker { } } - void onReadAudioDeviceSettings() { + private String readDeviceSettings() { final SettingsAdapter settingsAdapter = mAudioService.getSettings(); final ContentResolver contentResolver = mAudioService.getContentResolver(); - String settings = settingsAdapter.getSecureStringForUser(contentResolver, + return settingsAdapter.getSecureStringForUser(contentResolver, Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT); + } + + void onReadAudioDeviceSettings() { + final SettingsAdapter settingsAdapter = mAudioService.getSettings(); + final ContentResolver contentResolver = mAudioService.getContentResolver(); + String settings = readDeviceSettings(); if (settings == null) { Log.i(TAG, "reading AdiDeviceState from legacy key" + Settings.Secure.SPATIAL_AUDIO_ENABLED); @@ -2688,8 +2738,8 @@ public class AudioDeviceBroker { } @Nullable - AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) { - return mDeviceInventory.findBtDeviceStateForAddress(address, isBle); + AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) { + return mDeviceInventory.findBtDeviceStateForAddress(address, deviceType); } //------------------------------------------------ diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e59fd77919c5..7ba0827f2016 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -15,14 +15,23 @@ */ package com.android.server.audio; +import static android.media.AudioSystem.DEVICE_IN_ALL_SCO_SET; import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET; import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET; +import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO_SET; +import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID; +import static android.media.AudioSystem.isBluetoothA2dpOutDevice; import static android.media.AudioSystem.isBluetoothDevice; +import static android.media.AudioSystem.isBluetoothLeOutDevice; +import static android.media.AudioSystem.isBluetoothOutDevice; +import static android.media.AudioSystem.isBluetoothScoOutDevice; + import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.content.Intent; import android.media.AudioDeviceAttributes; @@ -72,7 +81,6 @@ import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; -import java.util.UUID; import java.util.stream.Stream; /** @@ -118,6 +126,7 @@ public class AudioDeviceInventory { return oldState; }); } + mDeviceBroker.postSynchronizeLeDevicesInInventory(deviceState); } /** @@ -125,23 +134,28 @@ public class AudioDeviceInventory { * Bluetooth device and no corresponding entry already exists. * @param ada the device to add if needed */ - void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) { - if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) { + void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddres) { + if (!isBluetoothOutDevice(deviceType)) { return; } synchronized (mDeviceInventoryLock) { - if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) { + AdiDeviceState ads = findBtDeviceStateForAddress(address, deviceType); + if (ads == null) { + ads = findBtDeviceStateForAddress(peerAddres, deviceType); + } + if (ads != null) { + mDeviceBroker.postSynchronizeLeDevicesInInventory(ads); return; } - AdiDeviceState ads = new AdiDeviceState( - ada.getType(), ada.getInternalType(), ada.getAddress()); + ads = new AdiDeviceState(AudioDeviceInfo.convertInternalDeviceToDeviceType(deviceType), + deviceType, address); mDeviceInventory.put(ads.getDeviceId(), ads); + mDeviceBroker.postPersistAudioDeviceSettings(); } - mDeviceBroker.persistAudioDeviceSettings(); } /** - * Adds a new AdiDeviceState or updates the audio device cateogory of the matching + * Adds a new AdiDeviceState or updates the audio device category of the matching * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. * @param deviceState the device to update */ @@ -152,6 +166,63 @@ public class AudioDeviceInventory { return oldState; }); } + mDeviceBroker.postSynchronizeLeDevicesInInventory(deviceState); + } + + /** + * synchronize AdiDeviceState for LE devices in the same group + */ + void onSynchronizeLeDevicesInInventory(AdiDeviceState updatedDevice) { + synchronized (mDevicesLock) { + synchronized (mDeviceInventoryLock) { + boolean found = false; + for (DeviceInfo di : mConnectedDevices.values()) { + if (di.mDeviceType != updatedDevice.getInternalDeviceType()) { + continue; + } + if (di.mDeviceAddress.equals(updatedDevice.getDeviceAddress())) { + for (AdiDeviceState ads2 : mDeviceInventory.values()) { + if (!(di.mDeviceType == ads2.getInternalDeviceType() + && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) { + continue; + } + ads2.setHasHeadTracker(updatedDevice.hasHeadTracker()); + ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled()); + ads2.setSAEnabled(updatedDevice.isSAEnabled()); + ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); + found = true; + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "onSynchronizeLeDevicesInInventory synced device pair ads1=" + + updatedDevice + " ads2=" + ads2).printLog(TAG)); + break; + } + } + if (di.mPeerDeviceAddress.equals(updatedDevice.getDeviceAddress())) { + for (AdiDeviceState ads2 : mDeviceInventory.values()) { + if (!(di.mDeviceType == ads2.getInternalDeviceType() + && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) { + continue; + } + ads2.setHasHeadTracker(updatedDevice.hasHeadTracker()); + ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled()); + ads2.setSAEnabled(updatedDevice.isSAEnabled()); + ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); + found = true; + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "onSynchronizeLeDevicesInInventory synced device pair ads1=" + + updatedDevice + " peer ads2=" + ads2).printLog(TAG)); + break; + } + } + if (found) { + break; + } + } + if (found) { + mDeviceBroker.postPersistAudioDeviceSettings(); + } + } + } } /** @@ -163,9 +234,21 @@ public class AudioDeviceInventory { * @return the found {@link AdiDeviceState} or {@code null} otherwise. */ @Nullable - AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) { + AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) { + Set<Integer> deviceSet; + if (isBluetoothA2dpOutDevice(deviceType)) { + deviceSet = DEVICE_OUT_ALL_A2DP_SET; + } else if (isBluetoothLeOutDevice(deviceType)) { + deviceSet = DEVICE_OUT_ALL_BLE_SET; + } else if (isBluetoothScoOutDevice(deviceType)) { + deviceSet = DEVICE_OUT_ALL_SCO_SET; + } else if (deviceType == DEVICE_OUT_HEARING_AID) { + deviceSet = new HashSet<>(); + deviceSet.add(DEVICE_OUT_HEARING_AID); + } else { + return null; + } synchronized (mDeviceInventoryLock) { - final Set<Integer> deviceSet = isBle ? DEVICE_OUT_ALL_BLE_SET : DEVICE_OUT_ALL_A2DP_SET; for (Integer internalType : deviceSet) { AdiDeviceState deviceState = mDeviceInventory.get( new Pair<>(internalType, address)); @@ -345,7 +428,8 @@ public class AudioDeviceInventory { final @NonNull String mDeviceName; final @NonNull String mDeviceAddress; int mDeviceCodecFormat; - final UUID mSensorUuid; + @NonNull String mPeerDeviceAddress; + final int mGroupId; /** Disabled operating modes for this device. Use a negative logic so that by default * an empty list means all modes are allowed. @@ -353,12 +437,13 @@ public class AudioDeviceInventory { @NonNull ArraySet<String> mDisabledModes = new ArraySet(0); DeviceInfo(int deviceType, String deviceName, String deviceAddress, - int deviceCodecFormat, @Nullable UUID sensorUuid) { + int deviceCodecFormat, String peerDeviceAddress, int groupId) { mDeviceType = deviceType; mDeviceName = deviceName == null ? "" : deviceName; mDeviceAddress = deviceAddress == null ? "" : deviceAddress; mDeviceCodecFormat = deviceCodecFormat; - mSensorUuid = sensorUuid; + mPeerDeviceAddress = peerDeviceAddress == null ? "" : peerDeviceAddress; + mGroupId = groupId; } void setModeDisabled(String mode) { @@ -379,7 +464,8 @@ public class AudioDeviceInventory { DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { - this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null); + this(deviceType, deviceName, deviceAddress, deviceCodecFormat, + null, BluetoothLeAudio.GROUP_ID_INVALID); } DeviceInfo(int deviceType, String deviceName, String deviceAddress) { @@ -393,7 +479,8 @@ public class AudioDeviceInventory { + ") name:" + mDeviceName + " addr:" + mDeviceAddress + " codec: " + Integer.toHexString(mDeviceCodecFormat) - + " sensorUuid: " + Objects.toString(mSensorUuid) + + " peer addr:" + mPeerDeviceAddress + + " group:" + mGroupId + " disabled modes: " + mDisabledModes + "]"; } @@ -714,6 +801,27 @@ public class AudioDeviceInventory { } } + + /*package*/ void onUpdateLeAudioGroupAddresses(int groupId) { + synchronized (mDevicesLock) { + for (DeviceInfo di : mConnectedDevices.values()) { + if (di.mGroupId == groupId) { + List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId); + if (di.mPeerDeviceAddress.equals("")) { + for (String addr : addresses) { + if (!addr.equals(di.mDeviceAddress)) { + di.mPeerDeviceAddress = addr; + break; + } + } + } else if (!addresses.contains(di.mPeerDeviceAddress)) { + di.mPeerDeviceAddress = ""; + } + } + } + } + } + /*package*/ void onReportNewRoutes() { int n = mRoutesObservers.beginBroadcast(); if (n > 0) { @@ -1419,7 +1527,7 @@ public class AudioDeviceInventory { if (!connect) { purgeDevicesRoles_l(); } else { - addAudioDeviceInInventoryIfNeeded(attributes); + addAudioDeviceInInventoryIfNeeded(device, address, ""); } } mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); @@ -1477,7 +1585,7 @@ public class AudioDeviceInventory { final ArraySet<String> toRemove = new ArraySet<>(); // Disconnect ALL DEVICE_OUT_HEARING_AID devices mConnectedDevices.values().forEach(deviceInfo -> { - if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { + if (deviceInfo.mDeviceType == DEVICE_OUT_HEARING_AID) { toRemove.add(deviceInfo.mDeviceAddress); } }); @@ -1485,8 +1593,8 @@ public class AudioDeviceInventory { .set(MediaMetrics.Property.EVENT, "disconnectHearingAid") .record(); if (toRemove.size() > 0) { - final int delay = checkSendBecomingNoisyIntentInt( - AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); + final int delay = checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID, + AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE); toRemove.stream().forEach(deviceAddress -> // TODO delay not used? makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) @@ -1687,12 +1795,8 @@ public class AudioDeviceInventory { // Reset A2DP suspend state each time a new sink is connected mDeviceBroker.clearA2dpSuspended(true /* internalOnly */); - // The convention for head tracking sensors associated with A2DP devices is to - // use a UUID derived from the MAC address as follows: - // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address - UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada); final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, - address, codec, sensorUuid); + address, codec); final String diKey = di.getKey(); mConnectedDevices.put(diKey, di); // on a connection always overwrite the device seen by AudioPolicy, see comment above when @@ -1703,7 +1807,7 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); - addAudioDeviceInInventoryIfNeeded(ada); + addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, ""); } static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, @@ -1723,15 +1827,15 @@ public class AudioDeviceInventory { return; } DeviceInfo leOutDevice = - getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET); + getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_BLE_SET); DeviceInfo leInDevice = getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET); DeviceInfo a2dpDevice = - getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); + getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_A2DP_SET); DeviceInfo scoOutDevice = - getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET); + getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_SCO_SET); DeviceInfo scoInDevice = - getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET); + getFirstConnectedDeviceOfTypes(DEVICE_IN_ALL_SCO_SET); boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled()); boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled()) || (leInDevice != null && leInDevice.isDuplexModeEnabled()); @@ -1765,7 +1869,7 @@ public class AudioDeviceInventory { continue; } - if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) { + if (isBluetoothOutDevice(di.mDeviceType)) { for (AudioProductStrategy strategy : mStrategies) { boolean disable = false; if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) { @@ -1832,23 +1936,20 @@ public class AudioDeviceInventory { int checkProfileIsConnected(int profile) { switch (profile) { case BluetoothProfile.HEADSET: - if (getFirstConnectedDeviceOfTypes( - AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null - || getFirstConnectedDeviceOfTypes( - AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) { + if (getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_SCO_SET) != null + || getFirstConnectedDeviceOfTypes(DEVICE_IN_ALL_SCO_SET) != null) { return profile; } break; case BluetoothProfile.A2DP: - if (getFirstConnectedDeviceOfTypes( - AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) { + if (getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_A2DP_SET) != null) { return profile; } break; case BluetoothProfile.LE_AUDIO: case BluetoothProfile.LE_AUDIO_BROADCAST: if (getFirstConnectedDeviceOfTypes( - AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null + DEVICE_OUT_ALL_BLE_SET) != null || getFirstConnectedDeviceOfTypes( AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) { return profile; @@ -2006,28 +2107,28 @@ public class AudioDeviceInventory { private void makeHearingAidDeviceAvailable( String address, String name, int streamType, String eventSource) { final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, - AudioSystem.DEVICE_OUT_HEARING_AID); + DEVICE_OUT_HEARING_AID); mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); AudioDeviceAttributes ada = new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_HEARING_AID, address, name); + DEVICE_OUT_HEARING_AID, address, name); mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( - DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), - new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address)); - mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); + DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address), + new DeviceInfo(DEVICE_OUT_HEARING_AID, name, address)); + mDeviceBroker.postAccessoryPlugMediaUnmute(DEVICE_OUT_HEARING_AID); mDeviceBroker.postApplyVolumeOnDevice(streamType, - AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); + DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); - addAudioDeviceInInventoryIfNeeded(ada); + addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, ""); new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") .set(MediaMetrics.Property.DEVICE, - AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) + AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID)) .set(MediaMetrics.Property.NAME, name) .set(MediaMetrics.Property.STREAM_TYPE, AudioSystem.streamToString(streamType)) @@ -2037,18 +2138,18 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeHearingAidDeviceUnavailable(String address) { AudioDeviceAttributes ada = new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_HEARING_AID, address); + DEVICE_OUT_HEARING_AID, address); mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove( - DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); + DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address)); // Remove Hearing Aid routes as well setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceUnavailable") .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") .set(MediaMetrics.Property.DEVICE, - AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) + AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID)) .record(); mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } @@ -2060,7 +2161,7 @@ public class AudioDeviceInventory { */ boolean isHearingAidConnected() { return getFirstConnectedDeviceOfTypes( - Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null; + Sets.newHashSet(DEVICE_OUT_HEARING_AID)) != null; } /** @@ -2102,6 +2203,20 @@ public class AudioDeviceInventory { final String address = btInfo.mDevice.getAddress(); String name = BtHelper.getName(btInfo.mDevice); + // Find LE Group ID and peer headset address if available + final int groupId = mDeviceBroker.getLeAudioDeviceGroupId(btInfo.mDevice); + String peerAddress = ""; + if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { + List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId); + if (addresses.size() > 1) { + for (String addr : addresses) { + if (!addr.equals(address)) { + peerAddress = addr; + break; + } + } + } + } // The BT Stack does not provide a name for LE Broadcast devices if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) { name = "Broadcast"; @@ -2127,14 +2242,12 @@ public class AudioDeviceInventory { } // Reset LEA suspend state each time a new sink is connected mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */); - - UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada); mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT, - sensorUuid)); + peerAddress, groupId)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); - addAudioDeviceInInventoryIfNeeded(ada); + addAudioDeviceInInventoryIfNeeded(device, address, peerAddress); } if (streamType == AudioSystem.STREAM_DEFAULT) { @@ -2226,12 +2339,12 @@ public class AudioDeviceInventory { BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI); BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE); - BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID); + BECOMING_NOISY_INTENT_DEVICES_SET.add(DEVICE_OUT_HEARING_AID); BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET); BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_BROADCAST); - BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); + BECOMING_NOISY_INTENT_DEVICES_SET.addAll(DEVICE_OUT_ALL_A2DP_SET); BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); - BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET); + BECOMING_NOISY_INTENT_DEVICES_SET.addAll(DEVICE_OUT_ALL_BLE_SET); } // must be called before removing the device from mConnectedDevices @@ -2512,16 +2625,22 @@ public class AudioDeviceInventory { mDevRoleCapturePresetDispatchers.finishBroadcast(); } - @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) { + List<String> getDeviceAddresses(AudioDeviceAttributes device) { + List<String> addresses = new ArrayList<String>(); final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(), device.getAddress()); synchronized (mDevicesLock) { DeviceInfo di = mConnectedDevices.get(key); - if (di == null) { - return null; + if (di != null) { + if (!di.mDeviceAddress.isEmpty()) { + addresses.add(di.mDeviceAddress); + } + if (!di.mPeerDeviceAddress.isEmpty()) { + addresses.add(di.mPeerDeviceAddress); + } } - return di.mSensorUuid; } + return addresses; } /*package*/ String getDeviceSettings() { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index d0ae0f25d6cd..99321c44931b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -237,7 +237,6 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.TreeSet; -import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -11054,7 +11053,9 @@ public class AudioService extends IAudioService.Stub final String addr = Objects.requireNonNull(address); - AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr, isBle); + AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr, + (isBle ? AudioSystem.DEVICE_OUT_BLE_HEADSET + : AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); int internalType = !isBle ? DEVICE_OUT_BLUETOOTH_A2DP : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES) @@ -11070,7 +11071,7 @@ public class AudioService extends IAudioService.Stub deviceState.setAudioDeviceCategory(btAudioDeviceCategory); mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState); - mDeviceBroker.persistAudioDeviceSettings(); + mDeviceBroker.postPersistAudioDeviceSettings(); mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes()); mSoundDoseHelper.setAudioDeviceCategory(addr, internalType, @@ -11084,7 +11085,8 @@ public class AudioService extends IAudioService.Stub super.getBluetoothAudioDeviceCategory_enforcePermission(); final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress( - Objects.requireNonNull(address), isBle); + Objects.requireNonNull(address), (isBle ? AudioSystem.DEVICE_OUT_BLE_HEADSET + : AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); if (deviceState == null) { return AUDIO_DEVICE_CATEGORY_UNKNOWN; } @@ -13596,8 +13598,8 @@ public class AudioService extends IAudioService.Stub return activeAssistantUids; } - UUID getDeviceSensorUuid(AudioDeviceAttributes device) { - return mDeviceBroker.getDeviceSensorUuid(device); + List<String> getDeviceAddresses(AudioDeviceAttributes device) { + return mDeviceBroker.getDeviceAddresses(device); } //====================== diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index cce6bd2938d1..7b9621581adf 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -26,7 +26,9 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothLeAudio; +import android.bluetooth.BluetoothLeAudioCodecStatus; import android.bluetooth.BluetoothProfile; +import android.content.Context; import android.content.Intent; import android.media.AudioDeviceAttributes; import android.media.AudioManager; @@ -43,6 +45,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.utils.EventLogger; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -57,9 +60,11 @@ public class BtHelper { private static final String TAG = "AS.BtHelper"; private final @NonNull AudioDeviceBroker mDeviceBroker; + private final @NonNull Context mContext; - BtHelper(@NonNull AudioDeviceBroker broker) { + BtHelper(@NonNull AudioDeviceBroker broker, Context context) { mDeviceBroker = broker; + mContext = context; } // BluetoothHeadset API to control SCO connection @@ -498,6 +503,32 @@ public class BtHelper { } } + // BluetoothLeAudio callback used to update the list of addresses in the same group as a + // connected LE Audio device + MyLeAudioCallback mLeAudioCallback = null; + + class MyLeAudioCallback implements BluetoothLeAudio.Callback { + @Override + public void onCodecConfigChanged(int groupId, + @NonNull BluetoothLeAudioCodecStatus status) { + // Do nothing + } + + @Override + public void onGroupNodeAdded(@NonNull BluetoothDevice device, int groupId) { + mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId); + } + + @Override + public void onGroupNodeRemoved(@NonNull BluetoothDevice device, int groupId) { + mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId); + } + @Override + public void onGroupStatusChanged(int groupId, int groupStatus) { + mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId); + } + } + // @GuardedBy("mDeviceBroker.mSetModeLock") @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { @@ -519,6 +550,11 @@ public class BtHelper { mHearingAid = (BluetoothHearingAid) proxy; break; case BluetoothProfile.LE_AUDIO: + if (mLeAudio == null) { + mLeAudioCallback = new MyLeAudioCallback(); + ((BluetoothLeAudio) proxy).registerCallback( + mContext.getMainExecutor(), mLeAudioCallback); + } mLeAudio = (BluetoothLeAudio) proxy; break; case BluetoothProfile.A2DP_SINK: @@ -977,6 +1013,28 @@ public class BtHelper { return result; } + /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) { + if (mLeAudio == null || device == null) { + return BluetoothLeAudio.GROUP_ID_INVALID; + } + return mLeAudio.getGroupId(device); + } + + /*package*/ List<String> getLeAudioGroupAddresses(int groupId) { + List<String> addresses = new ArrayList<String>(); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter == null || mLeAudio == null) { + return addresses; + } + List<BluetoothDevice> activeDevices = adapter.getActiveDevices(BluetoothProfile.LE_AUDIO); + for (BluetoothDevice device : activeDevices) { + if (device != null && mLeAudio.getGroupId(device) == groupId) { + addresses.add(device.getAddress()); + } + } + return addresses; + } + /** * Returns the String equivalent of the btCodecType. * diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 7abd9c7f750b..ea92154f2df0 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -564,7 +564,7 @@ public class SpatializerHelper { } if (updatedDevice != null) { onRoutingUpdated(); - mDeviceBroker.persistAudioDeviceSettings(); + mDeviceBroker.postPersistAudioDeviceSettings(); logDeviceState(updatedDevice, "addCompatibleAudioDevice"); } } @@ -614,7 +614,7 @@ public class SpatializerHelper { if (deviceState != null && deviceState.isSAEnabled()) { deviceState.setSAEnabled(false); onRoutingUpdated(); - mDeviceBroker.persistAudioDeviceSettings(); + mDeviceBroker.postPersistAudioDeviceSettings(); logDeviceState(deviceState, "removeCompatibleAudioDevice"); } } @@ -716,7 +716,7 @@ public class SpatializerHelper { ada.getAddress()); initSAState(deviceState); mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState); - mDeviceBroker.persistAudioDeviceSettings(); + mDeviceBroker.postPersistAudioDeviceSettings(); logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. } } @@ -1206,7 +1206,7 @@ public class SpatializerHelper { } Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); deviceState.setHeadTrackerEnabled(enabled); - mDeviceBroker.persistAudioDeviceSettings(); + mDeviceBroker.postPersistAudioDeviceSettings(); logDeviceState(deviceState, "setHeadTrackerEnabled"); // check current routing to see if it affects the headtracking mode @@ -1248,7 +1248,7 @@ public class SpatializerHelper { if (deviceState != null) { if (!deviceState.hasHeadTracker()) { deviceState.setHasHeadTracker(true); - mDeviceBroker.persistAudioDeviceSettings(); + mDeviceBroker.postPersistAudioDeviceSettings(); logDeviceState(deviceState, "setHasHeadTracker"); } return deviceState.isHeadTrackerEnabled(); @@ -1631,25 +1631,33 @@ public class SpatializerHelper { return headHandle; } final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); - UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice); + List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice); + // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by // SensorPoseProvider). // Note: this is a dynamic sensor list right now. List<Sensor> sensors = mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER); - for (Sensor sensor : sensors) { - final UUID uuid = sensor.getUuid(); - if (uuid.equals(routingDeviceUuid)) { - headHandle = sensor.getHandle(); - if (!setHasHeadTracker(currentDevice)) { - headHandle = -1; + for (String address : deviceAddresses) { + UUID routingDeviceUuid = UuidUtils.uuidFromAudioDeviceAttributes( + new AudioDeviceAttributes(currentDevice.getInternalType(), address)); + for (Sensor sensor : sensors) { + final UUID uuid = sensor.getUuid(); + if (uuid.equals(routingDeviceUuid)) { + headHandle = sensor.getHandle(); + if (!setHasHeadTracker(currentDevice)) { + headHandle = -1; + } + break; + } + if (uuid.equals(UuidUtils.STANDALONE_UUID)) { + headHandle = sensor.getHandle(); + // we do not break, perhaps we find a head tracker on device. } - break; } - if (uuid.equals(UuidUtils.STANDALONE_UUID)) { - headHandle = sensor.getHandle(); - // we do not break, perhaps we find a head tracker on device. + if (headHandle != -1) { + break; } } return headHandle; diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java index ad09ef0ccdc1..061b8ffa05a2 100644 --- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java @@ -79,7 +79,7 @@ public class SpatializerHelperTest { final AudioDeviceAttributes dev3 = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop"); - doNothing().when(mSpyDeviceBroker).persistAudioDeviceSettings(); + doNothing().when(mSpyDeviceBroker).postPersistAudioDeviceSettings(); mSpatHelper.initForTest(true /*binaural*/, true /*transaural*/); // test with single device |