summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-09 23:09:28 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-09 23:09:28 +0000
commit4b17cbcccf3c79b9839b32d055883b4fa12dac6c (patch)
tree671509816cac7f79d56c4515300ace3e6a1c33c4
parentc7d896b3b3e5aa58ec83546790cdd2f81ca32314 (diff)
parentd76065776b2690ee6c1595e46b10115ae0d32a42 (diff)
Snap for 8431966 from d76065776b2690ee6c1595e46b10115ae0d32a42 to tm-release
Change-Id: I0af90a0b5ca04edd75f394b617297a5f082d05f4
-rw-r--r--android/app/src/com/android/bluetooth/le_audio/LeAudioService.java257
-rw-r--r--android/leaudio/app/src/main/AndroidManifest.xml5
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java307
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java44
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java123
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java168
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java4
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java12
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java283
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java34
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java78
-rw-r--r--android/leaudio/app/src/main/res/layout/broadcast_scan_activity.xml39
-rw-r--r--android/leaudio/app/src/main/res/values/donottranslate_strings.xml (renamed from android/leaudio/app/src/main/res/values/strings.xml)7
-rw-r--r--framework/java/android/bluetooth/BluetoothHapClient.java4
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothManagerService.java3
-rw-r--r--system/blueberry/tests/gd/cert/matchers.py4
-rw-r--r--system/bta/include/bta_api.h2
-rw-r--r--system/bta/le_audio/client.cc2
-rw-r--r--system/bta/le_audio/le_audio_set_configuration_provider.h1
-rw-r--r--system/bta/le_audio/le_audio_set_configuration_provider_json.cc45
-rw-r--r--system/gd/btaa/linux_generic/hci_processor.cc2
-rw-r--r--system/gd/hci/facade/le_scanning_manager_facade.cc4
-rw-r--r--system/gd/hci/hci_packets.pdl36
-rw-r--r--system/gd/hci/le_scanning_manager.cc10
-rw-r--r--system/gd/hci/le_scanning_manager_test.cc6
-rw-r--r--system/gd/neighbor/inquiry.cc4
-rw-r--r--system/gd/packet/parser/doc/reference.md14
-rw-r--r--system/gd/rust/linux/client/src/command_handler.rs27
-rw-r--r--system/gd/rust/linux/client/src/dbus_iface.rs5
-rw-r--r--system/gd/rust/linux/service/src/iface_bluetooth.rs5
-rw-r--r--system/gd/rust/linux/service/src/main.rs33
-rw-r--r--system/gd/rust/linux/stack/src/bluetooth.rs46
-rw-r--r--system/gd/rust/topshim/src/btif.rs55
-rw-r--r--system/main/shim/btm.cc4
-rw-r--r--system/stack/btm/btm_ble.cc4
-rw-r--r--system/stack/btm/btm_ble_privacy.cc8
-rw-r--r--tools/rootcanal/model/controller/link_layer_controller.cc8
37 files changed, 1462 insertions, 231 deletions
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index d616ffcd83..b6b946a8a3 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -59,6 +59,7 @@ import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.mcp.McpService;
import com.android.bluetooth.vc.VolumeControlService;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.SynchronousResultReceiver;
@@ -110,9 +111,10 @@ public class LeAudioService extends ProfileService {
private AdapterService mAdapterService;
private DatabaseManager mDatabaseManager;
private HandlerThread mStateMachinesThread;
- private BluetoothDevice mActiveAudioOutDevice;
- private BluetoothDevice mActiveAudioInDevice;
+ private volatile BluetoothDevice mActiveAudioOutDevice;
+ private volatile BluetoothDevice mActiveAudioInDevice;
private LeAudioCodecConfig mLeAudioCodecConfig;
+ private Object mGroupLock = new Object();
ServiceFactory mServiceFactory = new ServiceFactory();
LeAudioNativeInterface mLeAudioNativeInterface;
@@ -143,9 +145,11 @@ public class LeAudioService extends ProfileService {
List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>();
List<BluetoothLeAudioCodecConfig> mOutputLocalCodecCapabilities = new ArrayList<>();
+ @GuardedBy("mGroupLock")
private final Map<Integer, LeAudioGroupDescriptor> mGroupDescriptors = new LinkedHashMap<>();
private final Map<BluetoothDevice, LeAudioStateMachine> mStateMachines = new LinkedHashMap<>();
+ @GuardedBy("mGroupLock")
private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>();
private final Map<BluetoothDevice, Integer> mDeviceAudioLocationMap = new ConcurrentHashMap<>();
@@ -210,13 +214,15 @@ public class LeAudioService extends ProfileService {
mStateMachinesThread = new HandlerThread("LeAudioService.StateMachines");
mStateMachinesThread.start();
- mDeviceGroupIdMap.clear();
mDeviceAudioLocationMap.clear();
mBroadcastStateMap.clear();
mBroadcastMetadataList.clear();
mBroadcastsPlaybackMap.clear();
- mGroupDescriptors.clear();
+ synchronized (mGroupLock) {
+ mDeviceGroupIdMap.clear();
+ mGroupDescriptors.clear();
+ }
// Setup broadcast receivers
IntentFilter filter = new IntentFilter();
@@ -247,12 +253,19 @@ public class LeAudioService extends ProfileService {
// Delay the call to init by posting it. This ensures TBS and MCS are fully initialized
// before we start accepting connections
- mHandler.post(() ->
- mLeAudioNativeInterface.init(mLeAudioCodecConfig.getCodecConfigOffloading()));
+ mHandler.post(this::init);
return true;
}
+ private void init() {
+ LeAudioNativeInterface nativeInterface = mLeAudioNativeInterface;
+ if (nativeInterface == null) {
+ Log.w(TAG, "the service is stopped. ignore init()");
+ }
+ nativeInterface.init(mLeAudioCodecConfig.getCodecConfigOffloading());
+ }
+
@Override
protected boolean stop() {
Log.i(TAG, "stop()");
@@ -264,15 +277,19 @@ public class LeAudioService extends ProfileService {
setActiveDevice(null);
//Don't wait for async call with INACTIVE group status, clean active
//device for active group.
- for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
- LeAudioGroupDescriptor descriptor = entry.getValue();
- Integer group_id = entry.getKey();
- if (descriptor.mIsActive) {
- descriptor.mIsActive = false;
- updateActiveDevices(group_id, descriptor.mActiveContexts,
- ACTIVE_CONTEXTS_NONE, descriptor.mIsActive);
- break;
+ synchronized (mGroupLock) {
+ for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
+ LeAudioGroupDescriptor descriptor = entry.getValue();
+ Integer group_id = entry.getKey();
+ if (descriptor.mIsActive) {
+ descriptor.mIsActive = false;
+ updateActiveDevices(group_id, descriptor.mActiveContexts,
+ ACTIVE_CONTEXTS_NONE, descriptor.mIsActive);
+ break;
+ }
}
+ mDeviceGroupIdMap.clear();
+ mGroupDescriptors.clear();
}
// Cleanup native interfaces
@@ -297,9 +314,7 @@ public class LeAudioService extends ProfileService {
mStateMachines.clear();
}
- mDeviceGroupIdMap.clear();
mDeviceAudioLocationMap.clear();
- mGroupDescriptors.clear();
if (mBroadcastCallbacks != null) {
mBroadcastCallbacks.kill();
@@ -328,7 +343,6 @@ public class LeAudioService extends ProfileService {
}
}
- mAudioManager = null;
mAdapterService = null;
mAudioManager = null;
@@ -423,7 +437,7 @@ public class LeAudioService extends ProfileService {
}
}
- public BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
+ BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
return getFirstDeviceFromGroup(groupId);
}
@@ -499,7 +513,7 @@ public class LeAudioService extends ProfileService {
* @param device the active device
* @return true on success, otherwise false
*/
- public boolean groupAddNode(int groupId, BluetoothDevice device) {
+ boolean groupAddNode(int groupId, BluetoothDevice device) {
return mLeAudioNativeInterface.groupAddNode(groupId, device);
}
@@ -509,7 +523,7 @@ public class LeAudioService extends ProfileService {
* @param device the active device
* @return true on success, otherwise false
*/
- public boolean groupRemoveNode(int groupId, BluetoothDevice device) {
+ boolean groupRemoveNode(int groupId, BluetoothDevice device) {
return mLeAudioNativeInterface.groupRemoveNode(groupId, device);
}
@@ -519,23 +533,25 @@ public class LeAudioService extends ProfileService {
* @return true given group exists, otherwise false
*/
public boolean isValidDeviceGroup(int group_id) {
- return (group_id != LE_AUDIO_GROUP_ID_INVALID) ?
- mDeviceGroupIdMap.containsValue(group_id) :
- false;
+ return group_id != LE_AUDIO_GROUP_ID_INVALID && mDeviceGroupIdMap.containsValue(group_id);
}
/**
* Get all the devices within a given group.
- * @param group_id group Id to verify
+ * @param groupId group id to get devices
* @return all devices within a given group or empty list
*/
- public List<BluetoothDevice> getGroupDevices(int group_id) {
+ public List<BluetoothDevice> getGroupDevices(int groupId) {
List<BluetoothDevice> result = new ArrayList<>();
- if (group_id != LE_AUDIO_GROUP_ID_INVALID) {
- for (BluetoothDevice storedDevice : mDeviceGroupIdMap.keySet()) {
- if (getGroupId(storedDevice) == group_id) {
- result.add(storedDevice);
+ if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
+ return result;
+ }
+
+ synchronized (mGroupLock) {
+ for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
+ if (entry.getValue() == groupId) {
+ result.add(entry.getKey());
}
}
}
@@ -545,11 +561,11 @@ public class LeAudioService extends ProfileService {
/**
* Get supported group audio direction from available context.
*
- * @param activeContext bitset of active context to be matched with possible audio direction
+ * @param activeContexts bitset of active context to be matched with possible audio direction
* support.
* @return matched possible audio direction support masked bitset
- * {@link AUDIO_DIRECTION_INPUT_BIT} if input audio is supported
- * {@link AUDIO_DIRECTION_OUTPUT_BIT} if output audio is supported
+ * {@link #AUDIO_DIRECTION_INPUT_BIT} if input audio is supported
+ * {@link #AUDIO_DIRECTION_OUTPUT_BIT} if output audio is supported
*/
private Integer getAudioDirectionsFromActiveContextsMap(Integer activeContexts) {
Integer supportedAudioDirections = 0;
@@ -565,10 +581,12 @@ public class LeAudioService extends ProfileService {
}
private Integer getActiveGroupId() {
- for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
- LeAudioGroupDescriptor descriptor = entry.getValue();
- if (descriptor.mIsActive) {
- return entry.getKey();
+ synchronized (mGroupLock) {
+ for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
+ LeAudioGroupDescriptor descriptor = entry.getValue();
+ if (descriptor.mIsActive) {
+ return entry.getKey();
+ }
}
}
return LE_AUDIO_GROUP_ID_INVALID;
@@ -669,21 +687,21 @@ public class LeAudioService extends ProfileService {
}
private BluetoothDevice getFirstDeviceFromGroup(Integer groupId) {
- if (groupId != LE_AUDIO_GROUP_ID_INVALID) {
- for(Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
+ if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
+ return null;
+ }
+ synchronized (mGroupLock) {
+ for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
if (entry.getValue() != groupId) {
continue;
}
-
LeAudioStateMachine sm = mStateMachines.get(entry.getKey());
if (sm == null || sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
continue;
}
-
return entry.getKey();
}
}
-
return null;
}
@@ -720,8 +738,10 @@ public class LeAudioService extends ProfileService {
}
} else if (previousGroupId != LE_AUDIO_GROUP_ID_INVALID) {
/* Mark old group as no active */
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(previousGroupId);
- descriptor.mIsActive = false;
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(previousGroupId);
+ if (descriptor != null) {
+ descriptor.mIsActive = false;
+ }
}
}
@@ -784,8 +804,10 @@ public class LeAudioService extends ProfileService {
} else if (previousGroupId != LE_AUDIO_GROUP_ID_INVALID) {
Log.i(TAG, " Switching active group from " + previousGroupId + " to " + groupId);
/* Mark old group as no active */
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(previousGroupId);
- descriptor.mIsActive = false;
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(previousGroupId);
+ if (descriptor != null) {
+ descriptor.mIsActive = false;
+ }
}
}
@@ -851,9 +873,8 @@ public class LeAudioService extends ProfileService {
/**
* Set the active device group.
- * @param groupId group Id to set active
*/
- private void setActiveDeviceGroup(BluetoothDevice device) {
+ private void setActiveGroupWithDevice(BluetoothDevice device) {
int groupId = LE_AUDIO_GROUP_ID_INVALID;
if (device != null) {
@@ -862,9 +883,9 @@ public class LeAudioService extends ProfileService {
int currentlyActiveGroupId = getActiveGroupId();
if (DBG) {
- Log.d(TAG, "setActiveDeviceGroup = " + groupId +
- ", currentlyActiveGroupId = " + currentlyActiveGroupId +
- ", device: " + device);
+ Log.d(TAG, "setActiveGroupWithDevice = " + groupId
+ + ", currentlyActiveGroupId = " + currentlyActiveGroupId
+ + ", device: " + device);
}
if (groupId == currentlyActiveGroupId) {
@@ -882,24 +903,22 @@ public class LeAudioService extends ProfileService {
* @return true on success, otherwise false
*/
public boolean setActiveDevice(BluetoothDevice device) {
- synchronized (mStateMachines) {
- /* Clear active group */
- if (device == null) {
- setActiveDeviceGroup(device);
- return true;
- }
- if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
- Log.e(TAG, "setActiveDevice(" + device + "): failed because group device is not " +
- "connected");
- return false;
- }
- setActiveDeviceGroup(device);
+ /* Clear active group */
+ if (device == null) {
+ setActiveGroupWithDevice(device);
return true;
}
+ if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
+ Log.e(TAG, "setActiveDevice(" + device + "): failed because group device is not "
+ + "connected");
+ return false;
+ }
+ setActiveGroupWithDevice(device);
+ return true;
}
/**
- * Get the active LE audio device.
+ * Get the active LE audio devices.
*
* Note: When LE audio group is active, one of the Bluetooth device address
* which belongs to the group, represents the active LE audio group.
@@ -912,17 +931,16 @@ public class LeAudioService extends ProfileService {
if (DBG) {
Log.d(TAG, "getActiveDevices");
}
- ArrayList<BluetoothDevice> activeDevices = new ArrayList<>();
+ ArrayList<BluetoothDevice> activeDevices = new ArrayList<>(2);
activeDevices.add(null);
activeDevices.add(null);
- synchronized (mStateMachines) {
- int currentlyActiveGroupId = getActiveGroupId();
- if (currentlyActiveGroupId == LE_AUDIO_GROUP_ID_INVALID) {
- return activeDevices;
- }
- activeDevices.add(0, mActiveAudioOutDevice);
- activeDevices.add(1, mActiveAudioInDevice);
+
+ int currentlyActiveGroupId = getActiveGroupId();
+ if (currentlyActiveGroupId == LE_AUDIO_GROUP_ID_INVALID) {
+ return activeDevices;
}
+ activeDevices.set(0, mActiveAudioOutDevice);
+ activeDevices.set(1, mActiveAudioInDevice);
return activeDevices;
}
@@ -957,9 +975,8 @@ public class LeAudioService extends ProfileService {
continue;
}
sm.sendMessage(LeAudioStateMachine.CONNECT);
- }
- }
-
+ }
+ }
}
// Suppressed since this is part of a local process
@@ -995,27 +1012,18 @@ public class LeAudioService extends ProfileService {
return;
}
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED) {
- int group_id = stackEvent.valueInt1;
- int node_status = stackEvent.valueInt2;
+ int groupId = stackEvent.valueInt1;
+ int nodeStatus = stackEvent.valueInt2;
Objects.requireNonNull(stackEvent.device,
"Device should never be null, event: " + stackEvent);
- switch (node_status) {
+ switch (nodeStatus) {
case LeAudioStackEvent.GROUP_NODE_ADDED:
- mDeviceGroupIdMap.put(device, group_id);
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(group_id);
- if (descriptor == null) {
- mGroupDescriptors.put(group_id, new LeAudioGroupDescriptor());
- }
- notifyGroupNodeAdded(device, group_id);
+ handleGroupNodeAdded(device, groupId);
break;
case LeAudioStackEvent.GROUP_NODE_REMOVED:
- mDeviceGroupIdMap.remove(device);
- if (mDeviceGroupIdMap.containsValue(group_id) == false) {
- mGroupDescriptors.remove(group_id);
- }
- notifyGroupNodeRemoved(device, group_id);
+ handleGroupNodeRemoved(device, groupId);
break;
default:
break;
@@ -1027,7 +1035,7 @@ public class LeAudioService extends ProfileService {
} else if (stackEvent.type
== LeAudioStackEvent.EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED) {
int groupId = stackEvent.valueInt1;
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
if (descriptor == null) {
Log.e(TAG, " Group not found " + groupId);
return;
@@ -1050,27 +1058,26 @@ public class LeAudioService extends ProfileService {
descriptor.mCodecStatus = status;
notifyUnicastCodecConfigChanged(groupId, status);
-
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED) {
int direction = stackEvent.valueInt1;
- int group_id = stackEvent.valueInt2;
+ int groupId = stackEvent.valueInt2;
int snk_audio_location = stackEvent.valueInt3;
int src_audio_location = stackEvent.valueInt4;
int available_contexts = stackEvent.valueInt5;
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(group_id);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
if (descriptor != null) {
if (descriptor.mIsActive) {
descriptor.mIsActive =
- updateActiveDevices(group_id, descriptor.mActiveContexts,
+ updateActiveDevices(groupId, descriptor.mActiveContexts,
available_contexts, descriptor.mIsActive);
if (!descriptor.mIsActive) {
- notifyGroupStatusChanged(group_id, BluetoothLeAudio.GROUP_STATUS_INACTIVE);
+ notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE);
}
}
descriptor.mActiveContexts = available_contexts;
} else {
- Log.e(TAG, "no descriptors for group: " + group_id);
+ Log.e(TAG, "no descriptors for group: " + groupId);
}
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE) {
Objects.requireNonNull(stackEvent.device,
@@ -1084,36 +1091,36 @@ public class LeAudioService extends ProfileService {
+ " audio location:" + sink_audio_location);
}
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED) {
- int group_id = stackEvent.valueInt1;
- int group_status = stackEvent.valueInt2;
+ int groupId = stackEvent.valueInt1;
+ int groupStatus = stackEvent.valueInt2;
boolean notifyGroupStatus = false;
- switch (group_status) {
+ switch (groupStatus) {
case LeAudioStackEvent.GROUP_STATUS_ACTIVE: {
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(group_id);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
if (descriptor != null) {
if (!descriptor.mIsActive) {
- descriptor.mIsActive = updateActiveDevices(group_id,
+ descriptor.mIsActive = updateActiveDevices(groupId,
ACTIVE_CONTEXTS_NONE,
descriptor.mActiveContexts, true);
notifyGroupStatus = descriptor.mIsActive;
}
} else {
- Log.e(TAG, "no descriptors for group: " + group_id);
+ Log.e(TAG, "no descriptors for group: " + groupId);
}
break;
}
case LeAudioStackEvent.GROUP_STATUS_INACTIVE: {
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(group_id);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
if (descriptor != null) {
if (descriptor.mIsActive) {
descriptor.mIsActive = false;
- updateActiveDevices(group_id, descriptor.mActiveContexts,
+ updateActiveDevices(groupId, descriptor.mActiveContexts,
ACTIVE_CONTEXTS_NONE, descriptor.mIsActive);
notifyGroupStatus = true;
}
} else {
- Log.e(TAG, "no descriptors for group: " + group_id);
+ Log.e(TAG, "no descriptors for group: " + groupId);
}
break;
}
@@ -1122,7 +1129,7 @@ public class LeAudioService extends ProfileService {
}
if (notifyGroupStatus) {
- notifyGroupStatusChanged(group_id, group_status);
+ notifyGroupStatusChanged(groupId, groupStatus);
}
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED) {
@@ -1340,7 +1347,7 @@ public class LeAudioService extends ProfileService {
// BluetoothMetricsProto.ProfileId.LE_AUDIO);
}
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(myGroupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
if (descriptor != null) {
descriptor.mIsConnected = true;
/* HearingAid activates device after connection
@@ -1372,7 +1379,7 @@ public class LeAudioService extends ProfileService {
}
int myGroupId = getGroupId(device);
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(myGroupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
if (descriptor == null) {
Log.e(TAG, "no descriptors for group: " + myGroupId);
return;
@@ -1520,7 +1527,9 @@ public class LeAudioService extends ProfileService {
if (device == null) {
return LE_AUDIO_GROUP_ID_INVALID;
}
- return mDeviceGroupIdMap.getOrDefault(device, LE_AUDIO_GROUP_ID_INVALID);
+ synchronized (mGroupLock) {
+ return mDeviceGroupIdMap.getOrDefault(device, LE_AUDIO_GROUP_ID_INVALID);
+ }
}
/**
@@ -1544,6 +1553,23 @@ public class LeAudioService extends ProfileService {
}
}
+ private LeAudioGroupDescriptor getGroupDescriptor(int groupId) {
+ synchronized (mGroupLock) {
+ return mGroupDescriptors.get(groupId);
+ }
+ }
+
+ private void handleGroupNodeAdded(BluetoothDevice device, int groupId) {
+ synchronized (mGroupLock) {
+ mDeviceGroupIdMap.put(device, groupId);
+ LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
+ if (descriptor == null) {
+ mGroupDescriptors.put(groupId, new LeAudioGroupDescriptor());
+ }
+ notifyGroupNodeAdded(device, groupId);
+ }
+ }
+
private void notifyGroupNodeAdded(BluetoothDevice device, int groupId) {
if (mLeAudioCallbacks != null) {
int n = mLeAudioCallbacks.beginBroadcast();
@@ -1558,6 +1584,16 @@ public class LeAudioService extends ProfileService {
}
}
+ private void handleGroupNodeRemoved(BluetoothDevice device, int groupId) {
+ synchronized (mGroupLock) {
+ mDeviceGroupIdMap.remove(device);
+ if (!mDeviceGroupIdMap.containsValue(groupId)) {
+ mGroupDescriptors.remove(groupId);
+ }
+ notifyGroupNodeRemoved(device, groupId);
+ }
+ }
+
private void notifyGroupNodeRemoved(BluetoothDevice device, int groupId) {
if (mLeAudioCallbacks != null) {
int n = mLeAudioCallbacks.beginBroadcast();
@@ -1741,7 +1777,7 @@ public class LeAudioService extends ProfileService {
if (DBG) {
Log.d(TAG, "getCodecStatus(" + groupId + ")");
}
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
if (descriptor != null) {
return descriptor.mCodecStatus;
}
@@ -1764,7 +1800,7 @@ public class LeAudioService extends ProfileService {
+ Objects.toString(inputCodecConfig)
+ Objects.toString(outputCodecConfig));
}
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
if (descriptor == null) {
Log.e(TAG, "setCodecConfigPreference: Invalid groupId, " + groupId);
return;
@@ -1793,7 +1829,6 @@ public class LeAudioService extends ProfileService {
inputCodecConfig, outputCodecConfig);
}
-
/**
* Binder object: must be a static class or memory leak may occur
*/
diff --git a/android/leaudio/app/src/main/AndroidManifest.xml b/android/leaudio/app/src/main/AndroidManifest.xml
index 996dcee9ff..120142d543 100644
--- a/android/leaudio/app/src/main/AndroidManifest.xml
+++ b/android/leaudio/app/src/main/AndroidManifest.xml
@@ -36,6 +36,11 @@
android:excludeFromRecents="true"
android:theme="@style/AppTheme.NoActionBar">
</activity>
+ <activity
+ android:name=".BroadcastScanActivity"
+ android:excludeFromRecents="true"
+ android:theme="@style/AppTheme.NoActionBar">
+ </activity>
</application>
</manifest>
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
index 0e628ab3df..0b931adb0a 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
@@ -26,40 +26,45 @@ import android.content.IntentFilter;
import android.os.ParcelUuid;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
-import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
-import com.android.bluetooth.leaudio.R;
-
public class BluetoothProxy {
private static BluetoothProxy INSTANCE;
private final Application application;
private final BluetoothAdapter bluetoothAdapter;
private BluetoothLeAudio bluetoothLeAudio = null;
private BluetoothLeBroadcast mBluetoothLeBroadcast = null;
+ private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant = null;
+ private Set<BluetoothDevice> mBroadcastScanOnBehalfDevices = new HashSet<>();
private BluetoothCsipSetCoordinator bluetoothCsis = null;
private BluetoothVolumeControl bluetoothVolumeControl = null;
private BluetoothHapClient bluetoothHapClient = null;
private BluetoothProfile.ServiceListener profileListener = null;
private BluetoothHapClient.Callback hapCallback = null;
+ private OnBassEventListener mBassEventListener;
+ private OnLocalBroadcastEventListener mLocalBroadcastEventListener;
private final IntentFilter adapterIntentFilter;
+ private final IntentFilter bassIntentFilter;
private IntentFilter intentFilter;
private final ExecutorService mExecutor;
@@ -290,6 +295,9 @@ public class BluetoothProxy {
}
mBroadcastAddedMutableLive.postValue(broadcastId);
+ if (mLocalBroadcastEventListener != null) {
+ mLocalBroadcastEventListener.onBroadcastStarted(broadcastId);
+ }
}
@Override
@@ -301,6 +309,9 @@ public class BluetoothProxy {
@Override
public void onBroadcastStopped(int reason, int broadcastId) {
mBroadcastRemovedMutableLive.postValue(new Pair<>(reason, broadcastId));
+ if (mLocalBroadcastEventListener != null) {
+ mLocalBroadcastEventListener.onBroadcastStopped(broadcastId);
+ }
}
@Override
@@ -323,6 +334,9 @@ public class BluetoothProxy {
public void onBroadcastUpdated(int reason, int broadcastId) {
mBroadcastStatusMutableLive.postValue("Broadcast " + broadcastId
+ "has been updated due to reason: " + reason);
+ if (mLocalBroadcastEventListener != null) {
+ mLocalBroadcastEventListener.onBroadcastUpdated(broadcastId);
+ }
}
@Override
@@ -335,9 +349,147 @@ public class BluetoothProxy {
public void onBroadcastMetadataChanged(int broadcastId,
BluetoothLeBroadcastMetadata metadata) {
mBroadcastUpdateMutableLive.postValue(metadata);
+ if (mLocalBroadcastEventListener != null) {
+ mLocalBroadcastEventListener.onBroadcastMetadataChanged(
+ broadcastId, metadata);
+ }
}
};
+ // TODO: Add behaviors in empty methods if necessary.
+ private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ new BluetoothLeBroadcastAssistant.Callback() {
+ @Override
+ public void onSearchStarted(int reason) {}
+
+ @Override
+ public void onSearchStartFailed(int reason) {}
+
+ @Override
+ public void onSearchStopped(int reason) {}
+
+ @Override
+ public void onSearchStopFailed(int reason) {}
+
+ @Override
+ public void onSourceFound(BluetoothLeBroadcastMetadata source) {
+ if (mBassEventListener != null) {
+ mBassEventListener.onSourceFound(source);
+ }
+ }
+
+ @Override
+ public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceAddFailed(BluetoothDevice sink,
+ BluetoothLeBroadcastMetadata source, int reason) {}
+
+ @Override
+ public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onReceiveStateChanged(BluetoothDevice sink, int sourceId,
+ BluetoothLeBroadcastReceiveState state) {
+ if (allLeAudioDevicesMutable.getValue() != null) {
+ Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
+ .getValue().stream()
+ .filter(stateWrapper -> stateWrapper.device.getAddress().equals(
+ sink.getAddress()))
+ .findAny();
+
+ if (!valid_device_opt.isPresent())
+ return;
+
+ LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
+ LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;
+
+ // TODO: Is the receiver_id same with BluetoothLeBroadcastReceiveState.getSourceId()?
+ // If not, find getSourceId() usages and fix the issues.
+// rstate.receiver_id = intent.getIntExtra(
+// BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID, -1);
+ /**
+ * From "Introducing-Bluetooth-LE-Audio-book" 8.6.3.1:
+ *
+ * The Source_ID is an Acceptor generated number which is used to identify a
+ * specific set of
+ * broadcast device and BIG information. It is local to an Acceptor and used as a
+ * reference for
+ * a Broadcast Assistant. In the case of a Coordinated Set of Acceptors, such as
+ * a left and right
+ * earbud, the Source_IDs are not related and may be different, even if both are
+ * receiving the
+ * same BIS, as each Acceptor independently creates their own Source ID values
+ */
+
+ /**
+ * From BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID:
+ *
+ * Broadcast receiver's endpoint identifier.
+ */
+
+ HashMap<Integer, BluetoothLeBroadcastReceiveState> states =
+ svc_data.receiverStatesMutable.getValue();
+ if (states == null)
+ states = new HashMap<>();
+ states.put(state.getSourceId(), state);
+
+ // Use SetValue instead of PostValue() since we want to make it
+ // synchronous due to getValue() we do here as well
+ // Otherwise we could miss the update and store only the last
+ // receiver ID
+ svc_data.receiverStatesMutable.setValue(states);
+ }
+ }
+ };
+
+ private final BroadcastReceiver bassIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED)) {
+ final BluetoothDevice device = intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
+
+ if (allLeAudioDevicesMutable.getValue() != null) {
+ if (device != null) {
+ Optional<LeAudioDeviceStateWrapper> valid_device_opt =
+ allLeAudioDevicesMutable
+ .getValue().stream()
+ .filter(state -> state.device.getAddress().equals(
+ device.getAddress()))
+ .findAny();
+
+ if (valid_device_opt.isPresent()) {
+ LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
+ LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;
+
+ final int toState = intent
+ .getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ if (toState == BluetoothProfile.STATE_CONNECTED
+ || toState == BluetoothProfile.STATE_DISCONNECTED)
+ svc_data.isConnectedMutable.postValue(
+ toState == BluetoothProfile.STATE_CONNECTED);
+ }
+ }
+ }
+ }
+ // TODO: Remove this if unnecessary.
+// case BluetoothBroadcastAudioScan.ACTION_BASS_BROADCAST_ANNONCEMENT_AVAILABLE:
+// // FIXME: Never happen since there is no valid device with this intent
+// break;
+ }
+ };
+
private BluetoothProxy(Application application) {
this.application = application;
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -360,6 +512,10 @@ public class BluetoothProxy {
adapterIntentFilter = new IntentFilter();
adapterIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
application.registerReceiver(adapterIntentReceiver, adapterIntentFilter);
+
+ bassIntentFilter = new IntentFilter();
+ bassIntentFilter.addAction(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED);
+ application.registerReceiver(bassIntentReceiver, bassIntentFilter);
}
// Lazy constructing Singleton acquire method
@@ -512,6 +668,12 @@ public class BluetoothProxy {
mBluetoothLeBroadcast = (BluetoothLeBroadcast) bluetoothProfile;
mBluetoothLeBroadcast.registerCallback(mExecutor, mBroadcasterCallback);
break;
+ case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
+ mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant)
+ bluetoothProfile;
+ mBluetoothLeBroadcastAssistant.registerCallback(mExecutor,
+ mBroadcastAssistantCallback);
+ break;
}
queryLeAudioDevices();
}
@@ -525,6 +687,7 @@ public class BluetoothProxy {
initVolumeControlProxy();
initHapProxy();
initLeAudioBroadcastProxy();
+ initBassProxy();
}
public void cleanupProfiles() {
@@ -535,6 +698,7 @@ public class BluetoothProxy {
cleanupVolumeControlProxy();
cleanupHapProxy();
cleanupLeAudioBroadcastProxy();
+ cleanupBassProxy();
profileListener = null;
}
@@ -605,6 +769,19 @@ public class BluetoothProxy {
}
}
+ private void initBassProxy() {
+ bluetoothAdapter.getProfileProxy(this.application, profileListener,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+ }
+
+ private void cleanupBassProxy() {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBluetoothLeBroadcastAssistant.unregisterCallback(mBroadcastAssistantCallback);
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
+ mBluetoothLeBroadcastAssistant);
+ }
+ }
+
private Boolean checkForEnabledBluetooth() {
Boolean current_state = bluetoothAdapter.isEnabled();
@@ -703,6 +880,21 @@ public class BluetoothProxy {
}
}
+ if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
+ .contains(ParcelUuid.fromString(
+ application.getString(R.string.svc_uuid_broadcast_audio)))) {
+ if (state_wrapper.bassData == null)
+ state_wrapper.bassData = new LeAudioDeviceStateWrapper.BassData();
+ valid_device = true;
+
+ if (mBluetoothLeBroadcastAssistant != null) {
+ boolean is_connected = mBluetoothLeBroadcastAssistant
+ .getConnectionState(dev) == BluetoothProfile.STATE_CONNECTED;
+ state_wrapper.bassData.isConnectedMutable.setValue(is_connected);
+ }
+ }
+
+
if (valid_device) validDevices.add(state_wrapper);
}
@@ -822,6 +1014,88 @@ public class BluetoothProxy {
}
}
+ public void connectBass(BluetoothDevice device, boolean connect) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ if (connect) {
+ mBluetoothLeBroadcastAssistant.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ } else {
+ mBluetoothLeBroadcastAssistant.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ }
+ }
+ }
+
+ public boolean scanForBroadcasts(@NonNull BluetoothDevice onBehalfDevice, boolean scan) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ // Note: startSearchingForSources() does not support scanning on behalf of
+ // a specific device - it only searches for all BASS connected devices.
+ // Therefore, we manage the list of the devices and start/stop the scanning.
+ if (scan) {
+ mBroadcastScanOnBehalfDevices.add(onBehalfDevice);
+ mBluetoothLeBroadcastAssistant.startSearchingForSources(new ArrayList<>());
+ if (mBassEventListener != null) {
+ mBassEventListener.onScanningStateChanged(true);
+ }
+ } else {
+ mBroadcastScanOnBehalfDevices.remove(onBehalfDevice);
+ if (mBroadcastScanOnBehalfDevices.isEmpty()) {
+ mBluetoothLeBroadcastAssistant.stopSearchingForSources();
+ if (mBassEventListener != null) {
+ mBassEventListener.onScanningStateChanged(false);
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean stopBroadcastObserving() {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBroadcastScanOnBehalfDevices.clear();
+ mBluetoothLeBroadcastAssistant.stopSearchingForSources();
+ if (mBassEventListener != null) {
+ mBassEventListener.onScanningStateChanged(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ // TODO: Uncomment this method if necessary
+// public boolean getBroadcastReceiverState(BluetoothDevice device, int receiver_id) {
+// if (mBluetoothLeBroadcastAssistant != null) {
+// return mBluetoothLeBroadcastAssistant.getBroadcastReceiverState(device, receiver_id);
+// }
+// return false;
+// }
+
+ public boolean addBroadcastSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBluetoothLeBroadcastAssistant.addSource(sink, sourceMetadata, true /* isGroupOp */);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean modifyBroadcastSource(BluetoothDevice sink, int sourceId,
+ BluetoothLeBroadcastMetadata metadata) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBluetoothLeBroadcastAssistant.modifySource(sink, sourceId, metadata);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean removeBroadcastSource(BluetoothDevice sink, int sourceId) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId);
+ return true;
+ }
+ return false;
+ }
+
public void setVolume(BluetoothDevice device, int volume) {
if (bluetoothLeAudio != null) {
bluetoothLeAudio.setVolume(volume);
@@ -843,7 +1117,7 @@ public class BluetoothProxy {
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
} else {
bluetoothHapClient.setConnectionPolicy(device,
- BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
}
}
@@ -1056,7 +1330,7 @@ public class BluetoothProxy {
return true;
}
- public List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
+ public List<BluetoothLeBroadcastMetadata> getAllLocalBroadcasts() {
if (mBluetoothLeBroadcast == null) return Collections.emptyList();
return mBluetoothLeBroadcast.getAllBroadcastMetadata();
}
@@ -1086,4 +1360,27 @@ public class BluetoothProxy {
return (bluetoothAdapter
.isLeAudioBroadcastSourceSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED);
}
+
+ public void setOnBassEventListener(OnBassEventListener listener) {
+ mBassEventListener = listener;
+ }
+
+ // Used by BroadcastScanViewModel
+ public interface OnBassEventListener {
+ void onSourceFound(BluetoothLeBroadcastMetadata source);
+ void onScanningStateChanged(boolean isScanning);
+ }
+
+ public void setOnLocalBroadcastEventListener(OnLocalBroadcastEventListener listener) {
+ mLocalBroadcastEventListener = listener;
+ }
+
+ // Used by BroadcastScanViewModel
+ public interface OnLocalBroadcastEventListener {
+ // TODO: Add arguments in methods
+ void onBroadcastStarted(int broadcastId);
+ void onBroadcastStopped(int broadcastId);
+ void onBroadcastUpdated(int broadcastId);
+ void onBroadcastMetadataChanged(int broadcastId, BluetoothLeBroadcastMetadata metadata);
+ }
}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
index 215d71b7da..e98e0a9c87 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
@@ -34,12 +34,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import com.android.bluetooth.leaudio.R;
-
public class BroadcastItemsAdapter
extends RecyclerView.Adapter<BroadcastItemsAdapter.BroadcastItemHolder> {
- private List<BluetoothLeBroadcastMetadata> mBroadcastMetadata = new ArrayList<>();
- private final Map<Integer /* broadcastId */, Boolean /* isPlaying */> mBroadcastPlayback =
+ private List<BluetoothLeBroadcastMetadata> mBroadcastMetadataList = new ArrayList<>();
+ private final Map<Integer /* broadcastId */, Boolean /* isPlaying */> mBroadcastPlaybackMap =
new HashMap<>();
private OnItemClickListener mOnItemClickListener;
@@ -57,8 +55,8 @@ public class BroadcastItemsAdapter
@Override
public void onBindViewHolder(@NonNull BroadcastItemHolder holder, int position) {
- Integer broadcastId = (Integer) mBroadcastPlayback.keySet().toArray()[position];
- Boolean isPlaying = mBroadcastPlayback.get(broadcastId);
+ Integer broadcastId = (Integer) mBroadcastPlaybackMap.keySet().toArray()[position];
+ Boolean isPlaying = mBroadcastPlaybackMap.get(broadcastId);
// Set card color based on the playback state
if (isPlaying) {
@@ -76,33 +74,47 @@ public class BroadcastItemsAdapter
@Override
public int getItemCount() {
- return mBroadcastPlayback.size();
+ return mBroadcastPlaybackMap.size();
}
public void updateBroadcastsMetadata(List<BluetoothLeBroadcastMetadata> broadcasts) {
- mBroadcastMetadata = broadcasts;
+ mBroadcastMetadataList = broadcasts;
notifyDataSetChanged();
}
public void updateBroadcastMetadata(BluetoothLeBroadcastMetadata broadcast) {
- mBroadcastMetadata.removeIf(bc -> (bc.getBroadcastId() == broadcast.getBroadcastId()));
- mBroadcastMetadata.add(broadcast);
+ mBroadcastMetadataList.removeIf(bc -> (bc.getBroadcastId() == broadcast.getBroadcastId()));
+ mBroadcastMetadataList.add(broadcast);
notifyDataSetChanged();
}
public void addBroadcasts(Integer broadcastId) {
- if (!mBroadcastPlayback.containsKey(broadcastId))
- mBroadcastPlayback.put(broadcastId, false);
+ if (!mBroadcastPlaybackMap.containsKey(broadcastId))
+ mBroadcastPlaybackMap.put(broadcastId, false);
}
public void removeBroadcast(Integer broadcastId) {
- mBroadcastMetadata.removeIf(bc -> (broadcastId.equals(bc.getBroadcastId())));
- mBroadcastPlayback.remove(broadcastId);
+ mBroadcastMetadataList.removeIf(bc -> (broadcastId.equals(bc.getBroadcastId())));
+ mBroadcastPlaybackMap.remove(broadcastId);
+ notifyDataSetChanged();
+ }
+
+ public void setBroadcasts(List<BluetoothLeBroadcastMetadata> broadcasts) {
+ mBroadcastMetadataList.clear();
+ mBroadcastMetadataList.addAll(broadcasts);
+
+ for (BluetoothLeBroadcastMetadata b : broadcasts) {
+ int broadcastId = b.getBroadcastId();
+ if (mBroadcastPlaybackMap.containsKey(broadcastId)) {
+ continue;
+ }
+ mBroadcastPlaybackMap.remove(broadcastId);
+ }
notifyDataSetChanged();
}
public void updateBroadcastPlayback(Integer broadcastId, boolean isPlaying) {
- mBroadcastPlayback.put(broadcastId, isPlaying);
+ mBroadcastPlaybackMap.put(broadcastId, isPlaying);
notifyDataSetChanged();
}
@@ -125,7 +137,7 @@ public class BroadcastItemsAdapter
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
- Integer broadcastId = (Integer) mBroadcastPlayback.keySet().toArray()[position];
+ Integer broadcastId = (Integer) mBroadcastPlaybackMap.keySet().toArray()[position];
listener.onItemClick(broadcastId);
}
});
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java
new file mode 100644
index 0000000000..17cf73893a
--- /dev/null
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.leaudio;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.Objects;
+
+
+public class BroadcastScanActivity extends AppCompatActivity {
+ // Integer key used for sending/receiving receiver ID.
+ public static final String EXTRA_BASS_RECEIVER_ID = "receiver_id";
+
+ private static final int BIS_ALL = 0xFFFFFFFF;
+
+ private BluetoothDevice device;
+ private BroadcastScanViewModel mViewModel;
+ private BroadcastItemsAdapter adapter;
+ private String mLocalBluetoothAddress;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.broadcast_scan_activity);
+
+ RecyclerView recyclerView = findViewById(R.id.broadcast_recycler_view);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ recyclerView.setHasFixedSize(true);
+
+ mLocalBluetoothAddress = BluetoothAdapter.getDefaultAdapter().getAddress();
+
+ adapter = new BroadcastItemsAdapter();
+ adapter.setOnItemClickListener(broadcastId -> {
+ mViewModel.scanForBroadcasts(device, false);
+
+ BluetoothLeBroadcastMetadata broadcast = null;
+ for (BluetoothLeBroadcastMetadata b : mViewModel.getAllBroadcasts().getValue()) {
+ if (Objects.equals(b.getBroadcastId(), broadcastId)) {
+ broadcast = b;
+ break;
+ }
+ }
+
+ if (broadcast == null) {
+ Toast.makeText(recyclerView.getContext(), "Matching broadcast not found."
+ + " broadcastId=" + broadcastId, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // TODO: Support selecting the subgroups instead of using all
+ mViewModel.addBroadcastSource(device, broadcast);
+ if (TextUtils.equals(mLocalBluetoothAddress, broadcast.getSourceDevice().getAddress())) {
+ Toast.makeText(recyclerView.getContext(), "Add local broadcast",
+ Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(recyclerView.getContext(), "Add remote broadcast",
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ recyclerView.setAdapter(adapter);
+
+ mViewModel = ViewModelProviders.of(this).get(BroadcastScanViewModel.class);
+ mViewModel.getAllBroadcasts().observe(this, audioBroadcasts -> {
+ // Update Broadcast list in the adapter
+ adapter.setBroadcasts(audioBroadcasts);
+ });
+
+ Intent intent = getIntent();
+ device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ mViewModel.scanForBroadcasts(device, false);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mViewModel.getAllBroadcasts().getValue() != null)
+ adapter.setBroadcasts(mViewModel.getAllBroadcasts().getValue());
+
+ mViewModel.scanForBroadcasts(device, true);
+ mViewModel.refreshBroadcasts();
+ }
+
+ @Override
+ public void onBackPressed() {
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ startActivity(intent);
+ finish();
+ }
+}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java
new file mode 100644
index 0000000000..3e352e3963
--- /dev/null
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.leaudio;
+
+import android.app.Application;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class BroadcastScanViewModel extends AndroidViewModel {
+ private final String TAG = "BroadcastScanViewModel";
+ boolean mIsActivityScanning = false;
+ BluetoothDevice mOnBehalfDevice;
+
+ // TODO: Remove these variables if they are unnecessary
+// // AddBroadcast context
+// BluetoothDevice mSetSrcTargetDevice;
+// List<BluetoothBroadcastAudioScanBaseConfig> mSetSrcConfigs;
+// boolean mSetSrcSyncPa;
+
+ BluetoothProxy mBluetooth;
+ Application mApplication;
+ private MutableLiveData<List<BluetoothLeBroadcastMetadata>> mAllBroadcasts = new MutableLiveData<>();
+ private HashMap<Integer, BluetoothLeBroadcastMetadata> mScanSessionBroadcasts = new HashMap<>();
+
+ private final BluetoothProxy.OnBassEventListener mBassEventListener =
+ new BluetoothProxy.OnBassEventListener() {
+ @Override
+ public void onSourceFound(BluetoothLeBroadcastMetadata source) {
+ mScanSessionBroadcasts.put(source.getBroadcastId(), source);
+ }
+
+ @Override
+ public void onScanningStateChanged(boolean isScanning) {
+ if (!isScanning) {
+ // Update the live broadcast list and clear scan session results
+ List<BluetoothLeBroadcastMetadata> localSessionBroadcasts =
+ mBluetooth.getAllLocalBroadcasts();
+ ArrayList<BluetoothLeBroadcastMetadata> new_arr;
+ if (localSessionBroadcasts != null) {
+ new_arr = new ArrayList<>(localSessionBroadcasts);
+ } else {
+ new_arr = new ArrayList<>();
+ }
+ new_arr.addAll(mScanSessionBroadcasts.values());
+ mAllBroadcasts.postValue(new_arr);
+
+ // Continue as long as the main activity wants
+ if (mIsActivityScanning) {
+ if (mOnBehalfDevice != null) {
+ mBluetooth.scanForBroadcasts(mOnBehalfDevice, true);
+ }
+ }
+ } else {
+ // FIXME: Clear won't work - it would auto-update the mutable and clear it as
+ // mutable uses reference to its values
+ mScanSessionBroadcasts = new HashMap<>();
+ }
+ }
+ };
+
+ private final BluetoothProxy.OnLocalBroadcastEventListener mLocalBroadcastEventListener =
+ new BluetoothProxy.OnLocalBroadcastEventListener() {
+ @Override
+ public void onBroadcastStarted(int broadcastId) {
+ // FIXME: We need a finer grain control over updating individual broadcast state
+ // and not just the entire list of broadcasts
+ refreshBroadcasts();
+ }
+
+ @Override
+ public void onBroadcastStopped(int broadcastId) {
+ refreshBroadcasts();
+ }
+
+ @Override
+ public void onBroadcastUpdated(int broadcastId) {
+ refreshBroadcasts();
+ }
+
+ @Override
+ public void onBroadcastMetadataChanged(int broadcastId,
+ BluetoothLeBroadcastMetadata metadata) {
+ refreshBroadcasts();
+ }
+ };
+
+ public BroadcastScanViewModel(@NonNull Application application) {
+ super(application);
+ mApplication = application;
+ mBluetooth = BluetoothProxy.getBluetoothProxy(application);
+
+ mBluetooth.setOnBassEventListener(mBassEventListener);
+ mBluetooth.setOnLocalBroadcastEventListener(mLocalBroadcastEventListener);
+ }
+
+ @Override
+ public void onCleared() {
+ mBluetooth.setOnBassEventListener(null);
+ mBluetooth.setOnLocalBroadcastEventListener(null);
+ }
+
+ public LiveData<List<BluetoothLeBroadcastMetadata>> getAllBroadcasts() {
+ return mAllBroadcasts;
+ }
+
+ public void scanForBroadcasts(BluetoothDevice device, boolean scan) {
+ if (device == null) {
+ Log.e(TAG, "scanForBroadcasts: device is null. Ignoring.");
+ return;
+ }
+
+ mIsActivityScanning = scan;
+ mOnBehalfDevice = scan ? device : null;
+
+ // First update the live broadcast list
+ List<BluetoothLeBroadcastMetadata> localSessionBroadcasts =
+ mBluetooth.getAllLocalBroadcasts();
+ ArrayList<BluetoothLeBroadcastMetadata> new_arr;
+ if (localSessionBroadcasts != null) {
+ new_arr = new ArrayList<>(localSessionBroadcasts);
+ } else {
+ new_arr = new ArrayList<>();
+ }
+ new_arr.addAll(mScanSessionBroadcasts.values());
+ mAllBroadcasts.postValue(new_arr);
+
+ mBluetooth.scanForBroadcasts(device, scan);
+ }
+
+ public void addBroadcastSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata) {
+ mBluetooth.addBroadcastSource(sink, sourceMetadata);
+ }
+
+ public void refreshBroadcasts() {
+ // Concatenate local broadcasts to the scanned broadcast list
+ List<BluetoothLeBroadcastMetadata> localSessionBroadcasts =
+ mBluetooth.getAllLocalBroadcasts();
+ ArrayList<BluetoothLeBroadcastMetadata> new_arr = new ArrayList<>(
+ localSessionBroadcasts);
+ new_arr.addAll(mScanSessionBroadcasts.values());
+ mAllBroadcasts.postValue(new_arr);
+ }
+}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java
index 94d4948930..bf22c8e73e 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java
@@ -56,11 +56,11 @@ public class BroadcasterViewModel extends AndroidViewModel {
}
public List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
- return mBluetooth.getAllBroadcastMetadata();
+ return mBluetooth.getAllLocalBroadcasts();
}
public int getBroadcastCount() {
- return mBluetooth.getAllBroadcastMetadata().size();
+ return mBluetooth.getAllLocalBroadcasts().size();
}
public LiveData<BluetoothLeBroadcastMetadata> getBroadcastUpdateMetadataLive() {
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java
index e94885684d..3183d6be3a 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java
@@ -19,6 +19,8 @@ package com.android.bluetooth.leaudio;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapPresetInfo;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
import androidx.core.util.Pair;
import androidx.lifecycle.MutableLiveData;
@@ -100,16 +102,10 @@ public class LeAudioDeviceStateWrapper {
public Object viewsData = null;
}
- public static class ReceiverState {
- int receiver_id;
- int state;
- }
-
public static class BassData {
- public MutableLiveData<Boolean> isValidBassDevice = new MutableLiveData<>();
public MutableLiveData<Boolean> isConnectedMutable = new MutableLiveData<>();
- public MutableLiveData<HashMap<Integer, ReceiverState>> receiverStatesMutable =
- new MutableLiveData<>();
+ public MutableLiveData<HashMap<Integer, BluetoothLeBroadcastReceiveState>>
+ receiverStatesMutable = new MutableLiveData<>();
public Object viewsData = null;
}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
index 6c8384b26a..74ae408528 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
@@ -17,17 +17,42 @@
package com.android.bluetooth.leaudio;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST;
+
+import static com.android.bluetooth.leaudio.BroadcastScanActivity.EXTRA_BASS_RECEIVER_ID;
+
import android.animation.ObjectAnimator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.content.Intent;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.os.ParcelUuid;
+import android.text.InputFilter;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.*;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.NumberPicker;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -39,14 +64,11 @@ import androidx.recyclerview.widget.RecyclerView;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
-import com.android.bluetooth.leaudio.R;
-
public class LeAudioRecycleViewAdapter
extends RecyclerView.Adapter<LeAudioRecycleViewAdapter.ViewHolder> {
private final AppCompatActivity parent;
@@ -99,6 +121,10 @@ public class LeAudioRecycleViewAdapter
holder.itemView.findViewById(R.id.hap_switch).setEnabled(
Arrays.asList(leAudioDeviceStateWrapper.device.getUuids()).contains(
ParcelUuid.fromString(parent.getString(R.string.svc_uuid_has))));
+
+ holder.itemView.findViewById(R.id.bass_switch)
+ .setEnabled(Arrays.asList(leAudioDeviceStateWrapper.device.getUuids())
+ .contains(ParcelUuid.fromString(parent.getString(R.string.svc_uuid_broadcast_audio))));
}
}
@@ -108,6 +134,7 @@ public class LeAudioRecycleViewAdapter
setVolumeControlUiStateObservers(holder, leAudioDeviceStateWrapper);
setBassStateObservers(holder, leAudioDeviceStateWrapper);
setHasStateObservers(holder, leAudioDeviceStateWrapper);
+ setBassUiStateObservers(holder, leAudioDeviceStateWrapper);
}
private void setLeAudioStateObservers(@NonNull ViewHolder holder,
@@ -626,6 +653,80 @@ public class LeAudioRecycleViewAdapter
}
}
+ private void setBassUiStateObservers(@NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
+ if (leAudioDeviceStateWrapper.bassData == null)
+ return;
+
+ ViewHolderBassPersistentData vData = (ViewHolderBassPersistentData)leAudioDeviceStateWrapper.bassData.viewsData;
+ if (vData == null)
+ return;
+
+ if (vData.selectedReceiverPositionMutable.hasObservers())
+ vData.selectedReceiverPositionMutable.removeObservers(this.parent);
+
+ vData.selectedReceiverPositionMutable.observe(this.parent, aInteger -> {
+ int receiver_id = Integer.parseInt(holder.bassReceiverIdSpinner.getItemAtPosition(aInteger).toString());
+ bassInteractionListener.onReceiverSelected(leAudioDeviceStateWrapper, receiver_id);
+
+ Map<Integer, BluetoothLeBroadcastReceiveState> states =
+ leAudioDeviceStateWrapper.bassData.receiverStatesMutable.getValue();
+
+ if (states != null) {
+ if (states.containsKey(receiver_id)) {
+ BluetoothLeBroadcastReceiveState state =
+ states.get(holder.bassReceiverIdSpinner.getSelectedItem());
+ final int paSyncState = state.getPaSyncState();
+ final int bigEncryptionState = state.getBigEncryptionState();
+
+ Resources res = this.parent.getResources();
+ String stateName = null;
+
+ // Set the icon
+ if (paSyncState == PA_SYNC_STATE_IDLE) {
+ holder.bassScanButton.setImageResource(R.drawable.ic_cast_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_idle);
+ } else if (paSyncState == PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE) {
+ holder.bassScanButton.setImageResource(R.drawable.ic_warning_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_sync_pa_failed);
+ } else if (paSyncState == PA_SYNC_STATE_SYNCHRONIZED) {
+ switch (bigEncryptionState) {
+ case BIG_ENCRYPTION_STATE_NOT_ENCRYPTED:
+ case BIG_ENCRYPTION_STATE_DECRYPTING:
+ holder.bassScanButton.setImageResource(
+ R.drawable.ic_bluetooth_searching_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_receiving_broadcast);
+ break;
+ case BIG_ENCRYPTION_STATE_CODE_REQUIRED:
+ holder.bassScanButton.setImageResource(
+ R.drawable.ic_vpn_key_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_code_required);
+ break;
+ case BIG_ENCRYPTION_STATE_BAD_CODE:
+ holder.bassScanButton.setImageResource(R.drawable.ic_warning_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_code_invalid);
+ break;
+ }
+ }
+
+ // TODO: Seems no appropriate state matching exists for RECEIVER_STATE_SYNCING
+ // and RECEIVER_STATE_SET_SOURCE_FAILED.
+ // What does "receiver source configuration has failed" mean?
+// else if (state == BluetoothBroadcastAudioScan.RECEIVER_STATE_SYNCING) {
+// holder.bassScanButton.setImageResource(R.drawable.ic_bluetooth_dots_black);
+// stateName = res.getString(R.string.broadcast_state_syncing);
+// }
+// } else if (state == BluetoothBroadcastAudioScan.RECEIVER_STATE_SET_SOURCE_FAILED) {
+// holder.bassScanButton.setImageResource(R.drawable.ic_refresh_black_24dp);
+// stateName = res.getString(R.string.broadcast_state_set_source_failed);
+// }
+
+ holder.bassReceiverStateText.setText(
+ stateName != null ? stateName : res.getString(R.string.unknown));
+ }
+ }
+ });
+ }
+
@Override
public long getItemId(int position) {
return devices.get(position).device.getAddress().hashCode();
@@ -766,21 +867,6 @@ public class LeAudioRecycleViewAdapter
int output_id, String description);
}
- public interface OnBassInteractionListener {
- void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
-
- void onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
-
- void onReceiverSelected(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
- int receiver_id);
-
- void onStopSyncReq(BluetoothDevice device, int receiver_id);
-
- void onRemoveSourceReq(BluetoothDevice device, int receiver_id);
-
- void onStopObserving();
- }
-
public interface OnHapInteractionListener {
void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
@@ -801,6 +887,22 @@ public class LeAudioRecycleViewAdapter
void onPreviousGroupPresetClicked(BluetoothDevice device);
}
+ public interface OnBassInteractionListener {
+ void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
+
+ void onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
+
+ void onReceiverSelected(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int receiver_id);
+
+ void onBroadcastCodeEntered(BluetoothDevice device, int receiver_id, byte[] broadcast_code);
+
+ void onStopSyncReq(BluetoothDevice device, int receiver_id);
+
+ void onRemoveSourceReq(BluetoothDevice device, int receiver_id);
+
+ void onStopObserving();
+ }
+
public class ViewHolder extends RecyclerView.ViewHolder {
private final TextView deviceName;
@@ -880,6 +982,7 @@ public class LeAudioRecycleViewAdapter
private Switch bassConnectionSwitch;
private Spinner bassReceiverIdSpinner;
private TextView bassReceiverStateText;
+ private ImageButton bassScanButton;
public ViewHolder(@NonNull View itemView) {
super(itemView);
@@ -888,6 +991,7 @@ public class LeAudioRecycleViewAdapter
SetupLeAudioView(itemView);
setupVcView(itemView);
setupHapView(itemView);
+ setupBassView(itemView);
// Notify viewmodel via parent's click listener
itemView.setOnClickListener(view -> {
@@ -1606,6 +1710,147 @@ public class LeAudioRecycleViewAdapter
}
});
}
+
+ private void setupBassView(@NonNull View itemView) {
+ bassConnectionSwitch = itemView.findViewById(R.id.bass_switch);
+ bassConnectionSwitch.setActivated(true);
+ bassReceiverIdSpinner = itemView.findViewById(R.id.num_receiver_spinner);
+ bassReceiverStateText = itemView.findViewById(R.id.receiver_state_text);
+ bassScanButton = itemView.findViewById(R.id.broadcast_button);
+
+ bassConnectionSwitch.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (!compoundButton.isActivated())
+ return;
+
+ if (bassInteractionListener != null) {
+ if (b)
+ bassInteractionListener.onConnectClick(
+ devices.get(ViewHolder.this.getAdapterPosition()));
+ else
+ bassInteractionListener.onDisconnectClick(
+ devices.get(ViewHolder.this.getAdapterPosition()));
+ }
+ });
+
+ bassReceiverIdSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
+ LeAudioDeviceStateWrapper device = devices.get(ViewHolder.this.getAdapterPosition());
+ ((ViewHolderBassPersistentData) device.bassData.viewsData).selectedReceiverPositionMutable.setValue(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView) {
+ // Nothing to do here
+ }
+ });
+
+ bassScanButton.setOnClickListener(view -> {
+ Resources res = view.getResources();
+
+ // TODO: Do not sync on the string value, but instead sync on the actual state value.
+ if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_idle))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Scan and add a source or remove the currently set one.");
+
+ BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+
+ alert.setPositiveButton("Scan", (dialog, whichButton) -> {
+ // Scan for new announcements
+ Intent intent = new Intent(this.itemView.getContext(), BroadcastScanActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.putExtra(EXTRA_BASS_RECEIVER_ID, receiver_id);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, devices.get(ViewHolder.this.getAdapterPosition()).device);
+ parent.startActivityForResult(intent, 666);
+ });
+ alert.setNeutralButton("Cancel", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.setNegativeButton("Remove", (dialog, whichButton) -> {
+ bassInteractionListener.onRemoveSourceReq(device, receiver_id);
+ });
+ alert.show();
+
+ } else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_code_required))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Please enter broadcast encryption code...");
+ EditText pass_input_view = new EditText(itemView.getContext());
+ pass_input_view.setFilters(new InputFilter[] { new InputFilter.LengthFilter(16) });
+ alert.setView(pass_input_view);
+
+ BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+
+ alert.setPositiveButton("Set", (dialog, whichButton) -> {
+ byte[] code = pass_input_view.getText().toString().getBytes();
+ bassInteractionListener.onBroadcastCodeEntered(device, receiver_id, code);
+ });
+ alert.setNegativeButton("Cancel", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.show();
+
+ } else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_receiving_broadcast))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Stop the synchronization?");
+
+ BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+
+ alert.setPositiveButton("Yes", (dialog, whichButton) -> {
+ bassInteractionListener.onRemoveSourceReq(device, receiver_id);
+ });
+ // FIXME: To modify source we need the valid broadcaster_id context so we should start scan here again
+ // alert.setNeutralButton("Modify", (dialog, whichButton) -> {
+ // // TODO: Open the scan dialog to get the broadcast_id
+ // // bassInteractionListener.onStopSyncReq(device, receiver_id, broadcast_id);
+ // });
+ alert.setNegativeButton("No", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.show();
+
+ } else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_set_source_failed))
+ || bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_sync_pa_failed))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Retry broadcast audio announcement scan?");
+
+ alert.setPositiveButton("Yes", (dialog, whichButton) -> {
+ // Scan for new announcements
+ Intent intent = new Intent(view.getContext(), BroadcastScanActivity.class);
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+ intent.putExtra(EXTRA_BASS_RECEIVER_ID, receiver_id);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, devices.get(ViewHolder.this.getAdapterPosition()).device);
+ parent.startActivityForResult(intent, 666);
+ });
+ alert.setNegativeButton("No", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.show();
+
+ } else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_syncing))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Stop the synchronization?");
+
+ BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+
+ alert.setPositiveButton("Yes", (dialog, whichButton) -> {
+ bassInteractionListener.onRemoveSourceReq(device, receiver_id);
+ });
+ // FIXME: To modify source we need the valid broadcaster_id context so we should start scan here again
+ // alert.setNeutralButton("Modify", (dialog, whichButton) -> {
+ // // TODO: Open the scan dialog to get the broadcast_id
+ // // bassInteractionListener.onStopSyncReq(device, receiver_id, broadcast_id);
+ // });
+ alert.setNegativeButton("No", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.show();
+ }
+ });
+ }
}
private class ViewHolderVcPersistentData {
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
index 3d6bf34fc8..ae31538b32 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
@@ -19,6 +19,7 @@ package com.android.bluetooth.leaudio;
import android.app.Application;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
@@ -115,4 +116,37 @@ public class LeAudioViewModel extends AndroidViewModel {
public boolean isLeAudioBroadcastSourceSupported() {
return bluetoothProxy.isLeAudioBroadcastSourceSupported();
}
+
+ public void connectBass(BluetoothDevice sink, boolean connect) {
+ bluetoothProxy.connectBass(sink, connect);
+ }
+
+ public boolean stopBroadcastObserving() {
+ return bluetoothProxy.stopBroadcastObserving();
+ }
+
+ // TODO: Uncomment this method if necessary
+// public boolean getBroadcastReceiverState(BluetoothDevice device, int receiver_id) {
+// return bluetoothProxy.getBroadcastReceiverState(device, receiver_id);
+// }
+
+ // TODO: Uncomment this method if necessary
+// public boolean modifyBroadcastSource(BluetoothDevice device, int receiver_id, boolean sync_pa,
+// List<BluetoothBroadcastAudioScanBaseConfig> configs) {
+// return bluetoothProxy.modifyBroadcastSource(device, receiver_id, sync_pa, configs);
+// }
+
+ public boolean removeBroadcastSource(BluetoothDevice sink, int receiver_id) {
+ // TODO: Find source ID from receiver_id. What is receiver_id?
+ int sourceId = 0;
+ return bluetoothProxy.removeBroadcastSource(sink, sourceId);
+ }
+
+ public boolean setBroadcastCode(BluetoothDevice sink, int receiver_id, byte[] bcast_code) {
+ // TODO: Find source ID from receiver_id. What is receiver_id?
+ // TODO: Build BluetoothLeBroadcastMetadata with the new bcast_code.
+ int sourceId = 0;
+ BluetoothLeBroadcastMetadata metadata = null;
+ return bluetoothProxy.modifyBroadcastSource(sink, sourceId, metadata);
+ }
}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java
index 33410073ee..fa7e8c45bc 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java
@@ -136,6 +136,21 @@ public class MainActivity extends AppCompatActivity {
Intent intent = null;
switch (item.getItemId()) {
+ case R.id.action_scan:
+ intent = new Intent(MainActivity.this, BroadcastScanActivity.class);
+ // TODO: Why does this pass no information?
+ //intent.putExtra(BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID, 0);
+
+ // TODO: Change BluetoothAdapter.getDefaultAdapter() usages into BluetoothManager#getAdapter().
+ BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ // What does this fake address mean?
+ byte[] address = {(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff};
+ BluetoothDevice dev = mAdapter.getRemoteDevice(address);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, dev);
+ startActivity(intent);
+ return true;
+
case R.id.action_broadcast:
if (leAudioViewModel.getBluetoothEnabledLive().getValue() == null
|| !leAudioViewModel.getBluetoothEnabledLive().getValue()) {
@@ -168,6 +183,9 @@ public class MainActivity extends AppCompatActivity {
Toast.makeText(MainActivity.this, message + "(" + resultCode + ")",
Toast.LENGTH_SHORT).show();
}
+
+ // TODO: Depending on the resultCode we should either stop the sync or try the PAST
+ leAudioViewModel.stopBroadcastObserving();
}
}
@@ -548,12 +566,72 @@ public class MainActivity extends AppCompatActivity {
.show();
}
});
+
+ recyclerViewAdapter.setOnBassInteractionListener(
+ new LeAudioRecycleViewAdapter.OnBassInteractionListener() {
+ @Override
+ public void onConnectClick(
+ LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
+ Toast.makeText(MainActivity.this,
+ "Connecting BASS to " + leAudioDeviceStateWrapper.device.toString(),
+ Toast.LENGTH_SHORT).show();
+ leAudioViewModel.connectBass(leAudioDeviceStateWrapper.device, true);
+ }
+
+ @Override
+ public void onDisconnectClick(
+ LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
+ Toast.makeText(MainActivity.this,
+ "Disconnecting BASS from "
+ + leAudioDeviceStateWrapper.device.toString(),
+ Toast.LENGTH_SHORT).show();
+ leAudioViewModel.connectBass(leAudioDeviceStateWrapper.device, false);
+ }
+
+ @Override
+ public void onReceiverSelected(
+ LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int receiver_id) {
+ // Do nothing here, the UI is updated elsewhere and we already have the
+ // latest state value as well
+ }
+
+ @Override
+ public void onBroadcastCodeEntered(BluetoothDevice device, int receiver_id,
+ byte[] broadcast_code) {
+ leAudioViewModel.setBroadcastCode(device, receiver_id, broadcast_code);
+ }
+
+ @Override
+ public void onStopSyncReq(BluetoothDevice device, int receiver_id) {
+ // TODO: When is onStopSyncReq called? and what does below code do?
+
+// List<BluetoothBroadcastAudioScanBaseConfig> configs = new ArrayList<>();
+// // JT@CC: How come you can call this with null metadata when the
+// // constructor has the @Nonull annotation for the param?
+// BluetoothBroadcastAudioScanBaseConfig stop_config =
+// new BluetoothBroadcastAudioScanBaseConfig(0, new byte[] {});
+// configs.add(stop_config);
+//
+// leAudioViewModel.modifyBroadcastSource(device, receiver_id, false, configs);
+ }
+
+ @Override
+ public void onRemoveSourceReq(BluetoothDevice device, int receiver_id) {
+ leAudioViewModel.removeBroadcastSource(device, receiver_id);
+ }
+
+ @Override
+ public void onStopObserving() {
+ leAudioViewModel.stopBroadcastObserving();
+ }
+ });
}
private void cleanupViewsProfileUiEventListeners() {
recyclerViewAdapter.setOnLeAudioInteractionListener(null);
recyclerViewAdapter.setOnVolumeControlInteractionListener(null);
recyclerViewAdapter.setOnHapInteractionListener(null);
+ recyclerViewAdapter.setOnBassInteractionListener(null);
}
// This sets the initial values and set up the observers
diff --git a/android/leaudio/app/src/main/res/layout/broadcast_scan_activity.xml b/android/leaudio/app/src/main/res/layout/broadcast_scan_activity.xml
new file mode 100644
index 0000000000..0d66e0a374
--- /dev/null
+++ b/android/leaudio/app/src/main/res/layout/broadcast_scan_activity.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".BroadcastScanActivity">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/scan_status_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:layout_marginTop="8dp"
+ android:text="Scanning for broadcasts..." />
+
+ <ProgressBar
+ android:id="@+id/progressBar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ android:indeterminateBehavior="cycle"
+ android:indeterminateOnly="true" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/broadcast_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:listitem="@layout/broadcast_item" />
+ </LinearLayout>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/android/leaudio/app/src/main/res/values/strings.xml b/android/leaudio/app/src/main/res/values/donottranslate_strings.xml
index 45d0add8da..0b7a060d99 100644
--- a/android/leaudio/app/src/main/res/values/strings.xml
+++ b/android/leaudio/app/src/main/res/values/donottranslate_strings.xml
@@ -79,6 +79,13 @@
</string-array>
<string name="group_locked">LOCKED</string>
<string name="group_unlocked">UNLOCKED</string>
+ <string name="broadcast_state_idle">IDLE</string>
+ <string name="broadcast_state_set_source_failed">SET_SOURCE_FAILED</string>
+ <string name="broadcast_state_syncing">SYNCING</string>
+ <string name="broadcast_state_sync_pa_failed">SYNC_PA_FAILED</string>
+ <string name="broadcast_state_code_required">BROADCAST_CODE_REQUIRED</string>
+ <string name="broadcast_state_code_invalid">BROADCAST_CODE_INVALID</string>
+ <string name="broadcast_state_receiving_broadcast">RECEIVING_BROADCAST</string>
<string-array name="tbs_call_states">
<item>Incoming</item>
<item>Dialing</item>
diff --git a/framework/java/android/bluetooth/BluetoothHapClient.java b/framework/java/android/bluetooth/BluetoothHapClient.java
index 5416d73610..1d51670ffe 100644
--- a/framework/java/android/bluetooth/BluetoothHapClient.java
+++ b/framework/java/android/bluetooth/BluetoothHapClient.java
@@ -644,6 +644,7 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ Objects.requireNonNull(device, "BluetoothDevice cannot be null");
final IBluetoothHapClient service = getService();
final boolean defaultValue = false;
if (service == null) {
@@ -673,7 +674,8 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
* {@link #CONNECTION_POLICY_UNKNOWN}
*
* @param device Bluetooth device
- * @return connection policy of the device
+ * @return connection policy of the device or {@link #CONNECTION_POLICY_FORBIDDEN} if device is
+ * null
* @hide
*/
@SystemApi
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index a1efcf3c63..4ee392f08d 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -1078,7 +1078,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub {
public boolean enableBle(AttributionSource attributionSource, IBinder token)
throws RemoteException {
final String packageName = attributionSource.getPackageName();
- if (!checkBluetoothPermissions(attributionSource, "enableBle", false)) {
+ if (!checkBluetoothPermissions(attributionSource, "enableBle", false)
+ || isAirplaneModeOn()) {
if (DBG) {
Log.d(TAG, "enableBle(): bluetooth disallowed");
}
diff --git a/system/blueberry/tests/gd/cert/matchers.py b/system/blueberry/tests/gd/cert/matchers.py
index 95dbe4dcfc..df15d7d4e4 100644
--- a/system/blueberry/tests/gd/cert/matchers.py
+++ b/system/blueberry/tests/gd/cert/matchers.py
@@ -242,7 +242,7 @@ class NeighborMatchers(object):
inquiry_view = hci_packets.InquiryResultView(hci_event)
if inquiry_view is None:
return False
- results = inquiry_view.GetInquiryResults()
+ results = inquiry_view.GetResponses()
return any((address == result.bd_addr for result in results))
@staticmethod
@@ -257,7 +257,7 @@ class NeighborMatchers(object):
inquiry_view = hci_packets.InquiryResultWithRssiView(hci_event)
if inquiry_view is None:
return False
- results = inquiry_view.GetInquiryResults()
+ results = inquiry_view.GetResponses()
return any((address == result.address for result in results))
@staticmethod
diff --git a/system/bta/include/bta_api.h b/system/bta/include/bta_api.h
index b3049c80ac..b98c8e4b5a 100644
--- a/system/bta/include/bta_api.h
+++ b/system/bta/include/bta_api.h
@@ -456,7 +456,7 @@ typedef struct {
BD_NAME bd_name; /* Name of peer device. */
tBTA_SERVICE_MASK services; /* Services found on peer device. */
tBT_DEVICE_TYPE device_type; /* device type in case it is BLE device */
- uint32_t num_uuids;
+ size_t num_uuids;
bluetooth::Uuid* p_uuid_list;
tBTA_STATUS result;
} tBTA_DM_DISC_RES;
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index fe0a4d06f0..0e46dc3443 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -37,6 +37,7 @@
#include "embdrv/lc3/include/lc3.h"
#include "gatt/bta_gattc_int.h"
#include "gd/common/strings.h"
+#include "le_audio_set_configuration_provider.h"
#include "le_audio_types.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
@@ -3628,6 +3629,7 @@ void LeAudioClient::DebugDump(int fd) {
LeAudioClientAudioSource::DebugDump(fd);
LeAudioClientAudioSink::DebugDump(fd);
+ le_audio::AudioSetConfigurationProvider::Get()->DebugDump(fd);
dprintf(fd, "\n");
}
diff --git a/system/bta/le_audio/le_audio_set_configuration_provider.h b/system/bta/le_audio/le_audio_set_configuration_provider.h
index 679135942e..7310868d68 100644
--- a/system/bta/le_audio/le_audio_set_configuration_provider.h
+++ b/system/bta/le_audio/le_audio_set_configuration_provider.h
@@ -28,6 +28,7 @@ class AudioSetConfigurationProvider {
virtual ~AudioSetConfigurationProvider() = default;
static AudioSetConfigurationProvider* Get();
static void Initialize();
+ static void DebugDump(int fd);
static void Cleanup();
virtual const set_configurations::AudioSetConfigurations* GetConfigurations(
::le_audio::types::LeAudioContextType content_type) const;
diff --git a/system/bta/le_audio/le_audio_set_configuration_provider_json.cc b/system/bta/le_audio/le_audio_set_configuration_provider_json.cc
index b2fa48156f..6e04c29f99 100644
--- a/system/bta/le_audio/le_audio_set_configuration_provider_json.cc
+++ b/system/bta/le_audio/le_audio_set_configuration_provider_json.cc
@@ -461,6 +461,36 @@ struct AudioSetConfigurationProvider::impl {
bool IsRunning() { return config_provider_impl_ ? true : false; }
+ void Dump(int fd) {
+ std::stringstream stream;
+
+ for (LeAudioContextType context : types::kLeAudioContextAllTypesArray) {
+ auto confs = Get()->GetConfigurations(context);
+ stream << " === Configurations for context type: " << (int)context
+ << ", num: " << (confs == nullptr ? 0 : confs->size()) << " \n";
+ if (confs->size() > 0) {
+ for (const auto& conf : *confs) {
+ stream << " name: " << conf->name << " \n";
+ for (const auto& ent : conf->confs) {
+ stream << " direction: "
+ << (ent.direction == types::kLeAudioDirectionSink
+ ? "Sink (speaker)\n"
+ : "Source (mic)\n")
+ << " number of devices: " << +ent.device_cnt << " \n"
+ << " number of ASEs: " << +ent.ase_cnt << " \n"
+ << " target latency: " << +ent.target_latency << " \n"
+ << " strategy: " << (int)(ent.strategy) << " \n"
+ << " qos->retransmission_number: "
+ << +ent.qos.retransmission_number << " \n"
+ << " qos->max_transport_latency: "
+ << +ent.qos.max_transport_latency << " \n";
+ }
+ }
+ }
+ }
+ dprintf(fd, "%s", stream.str().c_str());
+ }
+
const AudioSetConfigurationProvider& config_provider_;
std::unique_ptr<AudioSetConfigurationProviderJson> config_provider_impl_;
};
@@ -478,6 +508,21 @@ void AudioSetConfigurationProvider::Initialize() {
config_provider->pimpl_->Initialize();
}
+void AudioSetConfigurationProvider::DebugDump(int fd) {
+ if (!config_provider || !config_provider->pimpl_->IsRunning()) {
+ dprintf(
+ fd,
+ "\n AudioSetConfigurationProvider not initialized: config provider: "
+ "%d, pimpl: %d \n",
+ config_provider != nullptr,
+ (config_provider == nullptr ? 0
+ : config_provider->pimpl_->IsRunning()));
+ return;
+ }
+ dprintf(fd, "\n AudioSetConfigurationProvider: \n");
+ config_provider->pimpl_->Dump(fd);
+}
+
void AudioSetConfigurationProvider::Cleanup() {
if (!config_provider) return;
if (config_provider->pimpl_->IsRunning()) config_provider->pimpl_->Cleanup();
diff --git a/system/gd/btaa/linux_generic/hci_processor.cc b/system/gd/btaa/linux_generic/hci_processor.cc
index 60c5969bc9..28c1e70b19 100644
--- a/system/gd/btaa/linux_generic/hci_processor.cc
+++ b/system/gd/btaa/linux_generic/hci_processor.cc
@@ -74,7 +74,7 @@ void HciProcessor::process_special_event(
if (!packet_view.IsValid()) {
return;
}
- auto inquiry_results = packet_view.GetInquiryResults();
+ auto inquiry_results = packet_view.GetResponses();
avg_byte_count = byte_count / inquiry_results.size();
for (auto& inquiry_result : inquiry_results) {
btaa_hci_packets.push_back(BtaaHciPacket(Activity::SCAN, inquiry_result.bd_addr_, avg_byte_count));
diff --git a/system/gd/hci/facade/le_scanning_manager_facade.cc b/system/gd/hci/facade/le_scanning_manager_facade.cc
index 8e583e218b..ea0d058510 100644
--- a/system/gd/hci/facade/le_scanning_manager_facade.cc
+++ b/system/gd/hci/facade/le_scanning_manager_facade.cc
@@ -124,8 +124,8 @@ class LeScanningManagerFacadeService : public LeScanningManagerFacade::Service,
uint16_t periodic_advertising_interval,
std::vector<uint8_t> advertising_data) {
AdvertisingReportMsg advertising_report_msg;
- std::vector<LeExtendedAdvertisingReport> advertisements;
- LeExtendedAdvertisingReport le_extended_advertising_report;
+ std::vector<LeExtendedAdvertisingResponse> advertisements;
+ LeExtendedAdvertisingResponse le_extended_advertising_report;
le_extended_advertising_report.address_type_ = (DirectAdvertisingAddressType)address_type;
le_extended_advertising_report.address_ = address;
le_extended_advertising_report.advertising_data_ = advertising_data;
diff --git a/system/gd/hci/hci_packets.pdl b/system/gd/hci/hci_packets.pdl
index 2051261fc0..ed0a5318e3 100644
--- a/system/gd/hci/hci_packets.pdl
+++ b/system/gd/hci/hci_packets.pdl
@@ -4959,7 +4959,7 @@ packet InquiryComplete : Event (event_code = INQUIRY_COMPLETE) {
status : ErrorCode,
}
-struct InquiryResult {
+struct InquiryResponse {
bd_addr : Address,
page_scan_repetition_mode : PageScanRepetitionMode,
_reserved_ : 8,
@@ -4970,8 +4970,8 @@ struct InquiryResult {
}
packet InquiryResult : Event (event_code = INQUIRY_RESULT) {
- _count_(inquiry_results) : 8,
- inquiry_results : InquiryResult[],
+ _count_(responses) : 8,
+ responses : InquiryResponse[],
}
enum LinkType : 8 {
@@ -5197,7 +5197,7 @@ packet FlowSpecificationComplete : Event (event_code = FLOW_SPECIFICATION_COMPLE
access_latency : 32, // Octets/s
}
-struct InquiryResultWithRssi {
+struct InquiryResponseWithRssi {
address : Address,
page_scan_repetition_mode : PageScanRepetitionMode,
_reserved_ : 8,
@@ -5208,8 +5208,8 @@ struct InquiryResultWithRssi {
}
packet InquiryResultWithRssi : Event (event_code = INQUIRY_RESULT_WITH_RSSI) {
- _count_(inquiry_results) : 8,
- inquiry_results : InquiryResultWithRssi[],
+ _count_(responses) : 8,
+ responses : InquiryResponseWithRssi[],
}
packet ReadRemoteExtendedFeaturesComplete : Event (event_code = READ_REMOTE_EXTENDED_FEATURES_COMPLETE) {
@@ -5403,7 +5403,7 @@ enum AdvertisingEventType : 8 {
SCAN_RESPONSE = 0x04,
}
-struct LeAdvertisingReport {
+struct LeAdvertisingResponse {
event_type : AdvertisingEventType,
address_type : AddressType,
address : Address,
@@ -5413,11 +5413,11 @@ struct LeAdvertisingReport {
}
packet LeAdvertisingReport : LeMetaEvent (subevent_code = ADVERTISING_REPORT) {
- _count_(advertising_reports) : 8,
- advertising_reports : LeAdvertisingReport[],
+ _count_(responses) : 8,
+ responses : LeAdvertisingResponse[],
}
-struct LeAdvertisingReportRaw {
+struct LeAdvertisingResponseRaw {
event_type : AdvertisingEventType,
address_type : AddressType,
address : Address,
@@ -5427,8 +5427,8 @@ struct LeAdvertisingReportRaw {
}
packet LeAdvertisingReportRaw : LeMetaEvent (subevent_code = ADVERTISING_REPORT) {
- _count_(advertising_reports) : 8,
- advertising_reports : LeAdvertisingReportRaw[],
+ _count_(responses) : 8,
+ responses : LeAdvertisingResponseRaw[],
}
packet LeConnectionUpdateComplete : LeMetaEvent (subevent_code = CONNECTION_UPDATE_COMPLETE) {
@@ -5514,7 +5514,7 @@ enum DirectAddressType : 8 {
RANDOM_DEVICE_ADDRESS = 0x01,
}
-struct LeDirectedAdvertisingReport {
+struct LeDirectedAdvertisingResponse {
event_type : DirectAdvertisingEventType,
address_type : DirectAdvertisingAddressType,
address : Address,
@@ -5524,8 +5524,8 @@ struct LeDirectedAdvertisingReport {
}
packet LeDirectedAdvertisingReport : LeMetaEvent (subevent_code = DIRECTED_ADVERTISING_REPORT) {
- _count_(advertising_reports) : 8,
- advertising_reports : LeDirectedAdvertisingReport[],
+ _count_(responses) : 8,
+ responses : LeDirectedAdvertisingResponse[],
}
packet LePhyUpdateComplete : LeMetaEvent (subevent_code = PHY_UPDATE_COMPLETE) {
@@ -5543,7 +5543,7 @@ enum DataStatus : 2 {
RESERVED = 0x3,
}
-struct LeExtendedAdvertisingReport {
+struct LeExtendedAdvertisingResponse {
connectable : 1,
scannable : 1,
directed : 1,
@@ -5566,8 +5566,8 @@ struct LeExtendedAdvertisingReport {
}
packet LeExtendedAdvertisingReport : LeMetaEvent (subevent_code = EXTENDED_ADVERTISING_REPORT) {
- _count_(advertising_reports) : 8,
- advertising_reports : LeExtendedAdvertisingReport[],
+ _count_(responses) : 8,
+ responses : LeExtendedAdvertisingResponse[],
}
packet LePeriodicAdvertisingSyncEstablished : LeMetaEvent (subevent_code = PERIODIC_ADVERTISING_SYNC_ESTABLISHED) {
diff --git a/system/gd/hci/le_scanning_manager.cc b/system/gd/hci/le_scanning_manager.cc
index 5a742c8578..6ba1c42862 100644
--- a/system/gd/hci/le_scanning_manager.cc
+++ b/system/gd/hci/le_scanning_manager.cc
@@ -304,13 +304,13 @@ struct LeScanningManager::impl : public bluetooth::hci::LeAddressManagerCallback
LOG_INFO("Dropping invalid advertising event");
return;
}
- std::vector<LeAdvertisingReport> reports = event_view.GetAdvertisingReports();
+ std::vector<LeAdvertisingResponse> reports = event_view.GetResponses();
if (reports.empty()) {
LOG_INFO("Zero results in advertising event");
return;
}
- for (LeAdvertisingReport report : reports) {
+ for (LeAdvertisingResponse report : reports) {
uint16_t extended_event_type = 0;
switch (report.event_type_) {
case hci::AdvertisingEventType::ADV_IND:
@@ -362,7 +362,7 @@ struct LeScanningManager::impl : public bluetooth::hci::LeAddressManagerCallback
LOG_INFO("Dropping invalid advertising event");
return;
}
- std::vector<LeDirectedAdvertisingReport> reports = event_view.GetAdvertisingReports();
+ std::vector<LeDirectedAdvertisingResponse> reports = event_view.GetResponses();
if (reports.empty()) {
LOG_INFO("Zero results in advertising event");
return;
@@ -377,13 +377,13 @@ struct LeScanningManager::impl : public bluetooth::hci::LeAddressManagerCallback
LOG_INFO("Dropping invalid advertising event");
return;
}
- std::vector<LeExtendedAdvertisingReport> reports = event_view.GetAdvertisingReports();
+ std::vector<LeExtendedAdvertisingResponse> reports = event_view.GetResponses();
if (reports.empty()) {
LOG_INFO("Zero results in advertising event");
return;
}
- for (LeExtendedAdvertisingReport report : reports) {
+ for (LeExtendedAdvertisingResponse report : reports) {
uint16_t event_type = report.connectable_ | (report.scannable_ << kScannableBit) |
(report.directed_ << kDirectedBit) | (report.scan_response_ << kScanResponseBit) |
(report.legacy_ << kLegacyBit) | ((uint16_t)report.data_status_ << kDataStatusBits);
diff --git a/system/gd/hci/le_scanning_manager_test.cc b/system/gd/hci/le_scanning_manager_test.cc
index f5e6449295..bcf0c0287c 100644
--- a/system/gd/hci/le_scanning_manager_test.cc
+++ b/system/gd/hci/le_scanning_manager_test.cc
@@ -382,7 +382,7 @@ TEST_F(LeScanningManagerTest, start_scan_test) {
ASSERT_EQ(std::future_status::ready, result);
test_hci_layer_->IncomingEvent(LeSetScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
- LeAdvertisingReport report{};
+ LeAdvertisingResponse report{};
report.event_type_ = AdvertisingEventType::ADV_DIRECT_IND;
report.address_type_ = AddressType::PUBLIC_DEVICE_ADDRESS;
Address::FromString("12:34:56:78:9a:bc", report.address_);
@@ -409,7 +409,7 @@ TEST_F(LeAndroidHciScanningManagerTest, start_scan_test) {
ASSERT_EQ(std::future_status::ready, result);
test_hci_layer_->IncomingEvent(LeSetScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
- LeAdvertisingReport report{};
+ LeAdvertisingResponse report{};
report.event_type_ = AdvertisingEventType::ADV_DIRECT_IND;
report.address_type_ = AddressType::PUBLIC_DEVICE_ADDRESS;
Address::FromString("12:34:56:78:9a:bc", report.address_);
@@ -527,7 +527,7 @@ TEST_F(LeExtendedScanningManagerTest, start_scan_test) {
test_hci_layer_->IncomingEvent(LeSetScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
- LeExtendedAdvertisingReport report{};
+ LeExtendedAdvertisingResponse report{};
report.connectable_ = 1;
report.scannable_ = 0;
report.address_type_ = DirectAdvertisingAddressType::PUBLIC_DEVICE_ADDRESS;
diff --git a/system/gd/neighbor/inquiry.cc b/system/gd/neighbor/inquiry.cc
index 9eceebb366..f814fd0286 100644
--- a/system/gd/neighbor/inquiry.cc
+++ b/system/gd/neighbor/inquiry.cc
@@ -208,14 +208,14 @@ void neighbor::InquiryModule::impl::OnEvent(hci::EventView view) {
case hci::EventCode::INQUIRY_RESULT: {
auto packet = hci::InquiryResultView::Create(view);
ASSERT(packet.IsValid());
- LOG_INFO("Inquiry result size:%zd num_responses:%zu", packet.size(), packet.GetInquiryResults().size());
+ LOG_INFO("Inquiry result size:%zd num_responses:%zu", packet.size(), packet.GetResponses().size());
inquiry_callbacks_.result(packet);
} break;
case hci::EventCode::INQUIRY_RESULT_WITH_RSSI: {
auto packet = hci::InquiryResultWithRssiView::Create(view);
ASSERT(packet.IsValid());
- LOG_INFO("Inquiry result with rssi num_responses:%zu", packet.GetInquiryResults().size());
+ LOG_INFO("Inquiry result with rssi num_responses:%zu", packet.GetResponses().size());
inquiry_callbacks_.result_with_rssi(packet);
} break;
diff --git a/system/gd/packet/parser/doc/reference.md b/system/gd/packet/parser/doc/reference.md
index 1e521372d3..6158faee57 100644
--- a/system/gd/packet/parser/doc/reference.md
+++ b/system/gd/packet/parser/doc/reference.md
@@ -54,6 +54,20 @@ packet Brew {
}
```
+## Identifiers
+
+- Identifiers can denote a field; an enumeration tag; or a declared type.
+
+- Field identifiers declared in a [packet](#packet) (resp. [struct](#struct)) belong to the _scope_ that extends
+ to the packet (resp. struct), and all derived packets (resp. structs).
+
+- Field identifiers declared in a [group](#group) belong to the _scope_ that
+ extends to the packets declaring a [group field](#group_field) for this group.
+
+- Two fields may not be declared with the same identifier in any packet scope.
+
+- Two types may not be declared width the same identifier.
+
## Declarations
> declaration: {#declaration}\
diff --git a/system/gd/rust/linux/client/src/command_handler.rs b/system/gd/rust/linux/client/src/command_handler.rs
index f2221cc5e8..266dd5f261 100644
--- a/system/gd/rust/linux/client/src/command_handler.rs
+++ b/system/gd/rust/linux/client/src/command_handler.rs
@@ -437,7 +437,7 @@ impl CommandHandler {
return;
}
- enforce_arg_len(args, 2, "device <connect|disconnect|info> <address>", || {
+ enforce_arg_len(args, 2, "device <connect|disconnect|info|set-alias> <address>", || {
match &args[0][0..] {
"connect" => {
let device = BluetoothDevice {
@@ -519,6 +519,31 @@ impl CommandHandler {
)
);
}
+ "set-alias" => {
+ if args.len() < 3 {
+ println!("usage: device set-alias <address> <new-alias>");
+ return;
+ }
+ let new_alias = &args[2];
+ let device =
+ BluetoothDevice { address: String::from(&args[1]), name: String::from("") };
+ let old_alias = self
+ .context
+ .lock()
+ .unwrap()
+ .adapter_dbus
+ .as_ref()
+ .unwrap()
+ .get_remote_alias(device.clone());
+ println!("Updating alias for {}: {} -> {}", &args[1], old_alias, new_alias);
+ self.context
+ .lock()
+ .unwrap()
+ .adapter_dbus
+ .as_mut()
+ .unwrap()
+ .set_remote_alias(device.clone(), new_alias.clone());
+ }
_ => {
println!("Invalid argument '{}'", args[0]);
}
diff --git a/system/gd/rust/linux/client/src/dbus_iface.rs b/system/gd/rust/linux/client/src/dbus_iface.rs
index 300316f8dd..a18729e5ea 100644
--- a/system/gd/rust/linux/client/src/dbus_iface.rs
+++ b/system/gd/rust/linux/client/src/dbus_iface.rs
@@ -400,6 +400,11 @@ impl IBluetooth for BluetoothDBus {
dbus_generated!()
}
+ #[dbus_method("SetRemoteAlias")]
+ fn set_remote_alias(&mut self, device: BluetoothDevice, new_alias: String) {
+ dbus_generated!()
+ }
+
#[dbus_method("GetRemoteClass")]
fn get_remote_class(&self, device: BluetoothDevice) -> u32 {
dbus_generated!()
diff --git a/system/gd/rust/linux/service/src/iface_bluetooth.rs b/system/gd/rust/linux/service/src/iface_bluetooth.rs
index 37769b67b1..10229b2bbb 100644
--- a/system/gd/rust/linux/service/src/iface_bluetooth.rs
+++ b/system/gd/rust/linux/service/src/iface_bluetooth.rs
@@ -248,6 +248,11 @@ impl IBluetooth for IBluetoothDBus {
dbus_generated!()
}
+ #[dbus_method("SetRemoteAlias")]
+ fn set_remote_alias(&mut self, _device: BluetoothDevice, new_alias: String) {
+ dbus_generated!()
+ }
+
#[dbus_method("GetRemoteClass")]
fn get_remote_class(&self, _device: BluetoothDevice) -> u32 {
dbus_generated!()
diff --git a/system/gd/rust/linux/service/src/main.rs b/system/gd/rust/linux/service/src/main.rs
index 62b97f21f6..e749843e14 100644
--- a/system/gd/rust/linux/service/src/main.rs
+++ b/system/gd/rust/linux/service/src/main.rs
@@ -76,19 +76,6 @@ fn main() -> Result<(), Box<dyn Error>> {
let adapter_index = get_adapter_index(&args);
- // Hold locks and initialize all interfaces.
- {
- intf.lock().unwrap().initialize(get_bt_dispatcher(tx.clone()), args);
-
- bluetooth_media.lock().unwrap().set_adapter(bluetooth.clone());
-
- let mut bluetooth = bluetooth.lock().unwrap();
- bluetooth.init_profiles();
- bluetooth.enable();
-
- bluetooth_gatt.lock().unwrap().init_profiles(tx.clone());
- }
-
topstack::get_runtime().block_on(async {
// Connect to D-Bus system bus.
let (resource, conn) = connection::new_system_sync()?;
@@ -138,7 +125,7 @@ fn main() -> Result<(), Box<dyn Error>> {
make_object_name(adapter_index, "gatt"),
conn.clone(),
&mut cr,
- bluetooth_gatt,
+ bluetooth_gatt.clone(),
disconnect_watcher.clone(),
);
@@ -146,7 +133,7 @@ fn main() -> Result<(), Box<dyn Error>> {
make_object_name(adapter_index, "media"),
conn.clone(),
&mut cr,
- bluetooth_media,
+ bluetooth_media.clone(),
disconnect_watcher.clone(),
);
@@ -158,6 +145,22 @@ fn main() -> Result<(), Box<dyn Error>> {
disconnect_watcher.clone(),
);
+ // Hold locks and initialize all interfaces. This must be done AFTER DBus is
+ // initialized so DBus can properly enforce user policies.
+ {
+ intf.lock().unwrap().initialize(get_bt_dispatcher(tx.clone()), args);
+
+ bluetooth_media.lock().unwrap().set_adapter(bluetooth.clone());
+
+ let mut bluetooth = bluetooth.lock().unwrap();
+ bluetooth.init_profiles();
+ bluetooth.enable();
+
+ bluetooth_gatt.lock().unwrap().init_profiles(tx.clone());
+ }
+
+ // Start listening on DBus after exporting interfaces and initializing
+ // all bluetooth objects.
conn.start_receive(
MatchRule::new_method_call(),
Box::new(move |msg, conn| {
diff --git a/system/gd/rust/linux/stack/src/bluetooth.rs b/system/gd/rust/linux/stack/src/bluetooth.rs
index f2a610d83b..3728d1a04f 100644
--- a/system/gd/rust/linux/stack/src/bluetooth.rs
+++ b/system/gd/rust/linux/stack/src/bluetooth.rs
@@ -131,6 +131,9 @@ pub trait IBluetooth {
/// Gets the alias of the remote device.
fn get_remote_alias(&self, device: BluetoothDevice) -> String;
+ /// Sets the alias of the remote device.
+ fn set_remote_alias(&mut self, device: BluetoothDevice, new_alias: String);
+
/// Gets the class of the remote device.
fn get_remote_class(&self, device: BluetoothDevice) -> u32;
@@ -399,6 +402,16 @@ impl Bluetooth {
self.bonded_devices.get(&device.address).or_else(|| self.found_devices.get(&device.address))
}
+ fn get_remote_device_if_found_mut(
+ &mut self,
+ device: &BluetoothDevice,
+ ) -> Option<&mut BluetoothDeviceContext> {
+ match self.bonded_devices.get_mut(&device.address) {
+ None => self.found_devices.get_mut(&device.address),
+ some => some,
+ }
+ }
+
fn get_remote_device_property(
&self,
device: &BluetoothDevice,
@@ -407,6 +420,31 @@ impl Bluetooth {
self.get_remote_device_if_found(&device)
.and_then(|d| d.properties.get(property_type).and_then(|p| Some(p.clone())))
}
+
+ fn set_remote_device_property(
+ &mut self,
+ device: &BluetoothDevice,
+ property_type: BtPropertyType,
+ property: BluetoothProperty,
+ ) -> Result<(), ()> {
+ let mut remote_device = match self.get_remote_device_if_found_mut(&device) {
+ Some(d) => d,
+ None => {
+ return Err(());
+ }
+ };
+
+ let mut addr = RawAddress::from_string(device.address.clone());
+ if addr.is_none() {
+ return Err(());
+ }
+ let addr = addr.as_mut().unwrap();
+
+ // TODO: Determine why a callback isn't invoked to do this.
+ remote_device.properties.insert(property_type, property.clone());
+ self.intf.lock().unwrap().set_remote_device_property(addr, property);
+ Ok(())
+ }
}
#[btif_callbacks_dispatcher(Bluetooth, dispatch_base_callbacks, BaseCallbacks)]
@@ -1115,6 +1153,14 @@ impl IBluetooth for Bluetooth {
}
}
+ fn set_remote_alias(&mut self, device: BluetoothDevice, new_alias: String) {
+ let _ = self.set_remote_device_property(
+ &device,
+ BtPropertyType::RemoteFriendlyName,
+ BluetoothProperty::RemoteFriendlyName(new_alias),
+ );
+ }
+
fn get_remote_class(&self, device: BluetoothDevice) -> u32 {
match self.get_remote_device_property(&device, &BtPropertyType::ClassOfDevice) {
Some(BluetoothProperty::ClassOfDevice(class)) => return class,
diff --git a/system/gd/rust/topshim/src/btif.rs b/system/gd/rust/topshim/src/btif.rs
index 625f815b08..452d7a6e2d 100644
--- a/system/gd/rust/topshim/src/btif.rs
+++ b/system/gd/rust/topshim/src/btif.rs
@@ -382,7 +382,9 @@ impl BluetoothProperty {
let len = self.get_len();
match &*self {
BluetoothProperty::BdName(name) => {
- data.copy_from_slice(&name.as_bytes()[0..len]);
+ let copy_len = len - 1;
+ data[0..copy_len].copy_from_slice(&name.as_bytes()[0..copy_len]);
+ data[copy_len] = 0;
}
BluetoothProperty::BdAddr(addr) => {
data.copy_from_slice(&addr.val);
@@ -408,11 +410,12 @@ impl BluetoothProperty {
unsafe { &mut *(data.as_mut_ptr() as *mut bindings::bt_service_record_t) };
record.uuid = sr.uuid;
record.channel = sr.channel;
- let name_len = len - mem::size_of::<BtServiceRecord>();
- record.name.copy_from_slice(
+ let name_len = len - mem::size_of::<BtServiceRecord>() - 1;
+ record.name[0..name_len].copy_from_slice(
&(sr.name.as_bytes().iter().map(|x| *x as c_char).collect::<Vec<c_char>>())
[0..name_len],
);
+ record.name[name_len] = 0;
}
BluetoothProperty::AdapterScanMode(sm) => {
data.copy_from_slice(&BtScanMode::to_u32(sm).unwrap_or_default().to_ne_bytes());
@@ -428,7 +431,9 @@ impl BluetoothProperty {
data.copy_from_slice(&timeout.to_ne_bytes());
}
BluetoothProperty::RemoteFriendlyName(name) => {
- data.copy_from_slice(&name.as_bytes()[0..len]);
+ let copy_len = len - 1;
+ data[0..copy_len].copy_from_slice(&name.as_bytes()[0..copy_len]);
+ data[copy_len] = 0;
}
BluetoothProperty::RemoteRssi(rssi) => {
data[0] = *rssi as u8;
@@ -1110,4 +1115,46 @@ mod tests {
let expected: Vec<i32> = vec![1, 2, 3];
assert_eq!(expected, vec);
}
+
+ #[test]
+ fn test_property_with_string_conversions() {
+ {
+ let bdname = BluetoothProperty::BdName("FooBar".into());
+ let prop_pair: (Box<[u8]>, bindings::bt_property_t) = bdname.into();
+ let converted: BluetoothProperty = prop_pair.1.into();
+ assert!(match converted {
+ BluetoothProperty::BdName(name) => "FooBar".to_string() == name,
+ _ => false,
+ });
+ }
+
+ {
+ let orig_record = BtServiceRecord {
+ uuid: Uuid { uu: [0; 16] },
+ channel: 3,
+ name: "FooBar".to_string(),
+ };
+ let service_record = BluetoothProperty::ServiceRecord(orig_record.clone());
+ let prop_pair: (Box<[u8]>, bindings::bt_property_t) = service_record.into();
+ let converted: BluetoothProperty = prop_pair.1.into();
+ assert!(match converted {
+ BluetoothProperty::ServiceRecord(sr) => {
+ sr.uuid == orig_record.uuid
+ && sr.channel == orig_record.channel
+ && sr.name == orig_record.name
+ }
+ _ => false,
+ });
+ }
+
+ {
+ let rfname = BluetoothProperty::RemoteFriendlyName("FooBizz".into());
+ let prop_pair: (Box<[u8]>, bindings::bt_property_t) = rfname.into();
+ let converted: BluetoothProperty = prop_pair.1.into();
+ assert!(match converted {
+ BluetoothProperty::RemoteFriendlyName(name) => "FooBizz".to_string() == name,
+ _ => false,
+ });
+ }
+ }
}
diff --git a/system/main/shim/btm.cc b/system/main/shim/btm.cc
index 3ffd99aad6..e28f16b566 100644
--- a/system/main/shim/btm.cc
+++ b/system/main/shim/btm.cc
@@ -174,7 +174,7 @@ Btm::Btm(os::Handler* handler, neighbor::InquiryModule* inquiry)
}
void Btm::OnInquiryResult(bluetooth::hci::InquiryResultView view) {
- for (auto& response : view.GetInquiryResults()) {
+ for (auto& response : view.GetResponses()) {
btm_api_process_inquiry_result(
ToRawAddress(response.bd_addr_),
static_cast<uint8_t>(response.page_scan_repetition_mode_),
@@ -184,7 +184,7 @@ void Btm::OnInquiryResult(bluetooth::hci::InquiryResultView view) {
void Btm::OnInquiryResultWithRssi(
bluetooth::hci::InquiryResultWithRssiView view) {
- for (auto& response : view.GetInquiryResults()) {
+ for (auto& response : view.GetResponses()) {
btm_api_process_inquiry_result_with_rssi(
ToRawAddress(response.address_),
static_cast<uint8_t>(response.page_scan_repetition_mode_),
diff --git a/system/stack/btm/btm_ble.cc b/system/stack/btm/btm_ble.cc
index dfb136d90c..55d7097587 100644
--- a/system/stack/btm/btm_ble.cc
+++ b/system/stack/btm/btm_ble.cc
@@ -141,8 +141,8 @@ void BTM_SecAddBleKey(const RawAddress& bd_addr, tBTM_LE_KEY_VALUE* p_le_key,
key_type);
btm_sec_save_le_key(bd_addr, key_type, p_le_key, false);
-
- if (key_type == BTM_LE_KEY_PID || key_type == BTM_LE_KEY_LID) {
+ // Only set peer irk. Local irk is always the same.
+ if (key_type == BTM_LE_KEY_PID) {
btm_ble_resolving_list_load_dev(*p_dev_rec);
}
}
diff --git a/system/stack/btm/btm_ble_privacy.cc b/system/stack/btm/btm_ble_privacy.cc
index 78e981c41b..f5e2b78f3f 100644
--- a/system/stack/btm/btm_ble_privacy.cc
+++ b/system/stack/btm/btm_ble_privacy.cc
@@ -647,10 +647,6 @@ static void btm_ble_ble_unsupported_resolving_list_load_dev(
return;
}
-static bool is_local_identity_key_valid(const tBTM_SEC_DEV_REC& dev_rec) {
- return dev_rec.ble.key_type & BTM_LE_KEY_LID;
-}
-
static bool is_peer_identity_key_valid(const tBTM_SEC_DEV_REC& dev_rec) {
return dev_rec.ble.key_type & BTM_LE_KEY_PID;
}
@@ -667,8 +663,8 @@ void btm_ble_resolving_list_load_dev(tBTM_SEC_DEV_REC& dev_rec) {
return btm_ble_ble_unsupported_resolving_list_load_dev(&dev_rec);
}
- if (!is_local_identity_key_valid(dev_rec) &&
- !is_peer_identity_key_valid(dev_rec)) {
+ // No need to check for local identity key validity. It remains unchanged.
+ if (!is_peer_identity_key_valid(dev_rec)) {
LOG_INFO("Peer is not an RPA enabled device:%s",
PRIVATE_ADDRESS(dev_rec.ble.identity_address_with_type));
return;
diff --git a/tools/rootcanal/model/controller/link_layer_controller.cc b/tools/rootcanal/model/controller/link_layer_controller.cc
index b4764cb6bd..655dcd0a87 100644
--- a/tools/rootcanal/model/controller/link_layer_controller.cc
+++ b/tools/rootcanal/model/controller/link_layer_controller.cc
@@ -799,7 +799,7 @@ void LinkLayerController::IncomingInquiryResponsePacket(
(bluetooth::hci::PageScanRepetitionMode)
inquiry_response.GetPageScanRepetitionMode();
- std::vector<bluetooth::hci::InquiryResult> responses;
+ std::vector<bluetooth::hci::InquiryResponse> responses;
responses.emplace_back();
responses.back().bd_addr_ = inquiry_response.GetSourceAddress();
responses.back().page_scan_repetition_mode_ = page_scan_repetition_mode;
@@ -820,7 +820,7 @@ void LinkLayerController::IncomingInquiryResponsePacket(
(bluetooth::hci::PageScanRepetitionMode)
inquiry_response.GetPageScanRepetitionMode();
- std::vector<bluetooth::hci::InquiryResultWithRssi> responses;
+ std::vector<bluetooth::hci::InquiryResponseWithRssi> responses;
responses.emplace_back();
responses.back().address_ = inquiry_response.GetSourceAddress();
responses.back().page_scan_repetition_mode_ = page_scan_repetition_mode;
@@ -1825,7 +1825,7 @@ void LinkLayerController::IncomingLeScanResponsePacket(
if (adv_type != model::packets::AdvertisementType::SCAN_RESPONSE) {
return;
}
- bluetooth::hci::LeAdvertisingReportRaw report;
+ bluetooth::hci::LeAdvertisingResponseRaw report;
report.event_type_ = bluetooth::hci::AdvertisingEventType::SCAN_RESPONSE;
report.address_ = incoming.GetSourceAddress();
report.address_type_ =
@@ -1845,7 +1845,7 @@ void LinkLayerController::IncomingLeScanResponsePacket(
properties_.IsUnmasked(EventCode::LE_META_EVENT) &&
properties_.GetLeEventSupported(
bluetooth::hci::SubeventCode::EXTENDED_ADVERTISING_REPORT)) {
- bluetooth::hci::LeExtendedAdvertisingReport report{};
+ bluetooth::hci::LeExtendedAdvertisingResponse report{};
report.address_ = incoming.GetSourceAddress();
report.address_type_ =
static_cast<bluetooth::hci::DirectAdvertisingAddressType>(address_type);