Loading android/app/jni/com_android_bluetooth_le_audio.cpp +8 −16 Original line number Diff line number Diff line Loading @@ -82,27 +82,19 @@ class LeAudioClientCallbacksImpl : public LeAudioClientCallbacks { (jint)group_flags); } void OnAudioConf(const RawAddress& bd_addr, uint8_t direction, uint8_t group_id, uint32_t sink_audio_location, uint32_t source_audio_location) override { void OnAudioConf(uint8_t direction, int group_id, uint32_t sink_audio_location, uint32_t source_audio_location, uint16_t avail_cont) override { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; ScopedLocalRef<jbyteArray> addr( sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); if (!addr.get()) { LOG(ERROR) << "Failed to new jbyteArray bd addr for group status"; return; } sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), (jbyte*)&bd_addr); sCallbackEnv->CallVoidMethod( mCallbacksObj, method_onAudioConf, (jint)direction, (jint)group_id, (jint)sink_audio_location, (jint)source_audio_location, addr.get()); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioConf, (jint)direction, (jint)group_id, (jint)sink_audio_location, (jint)source_audio_location, (jint)avail_cont); } void OnSetMemberAvailable(const RawAddress& bd_addr, Loading Loading @@ -131,7 +123,7 @@ static LeAudioClientCallbacksImpl sLeAudioClientCallbacks; static void classInitNative(JNIEnv* env, jclass clazz) { method_onGroupStatus = env->GetMethodID(clazz, "onGroupStatus", "(III)V"); method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIII[B)V"); method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIIII)V"); method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); method_onSetMemberAvailable = Loading android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java +15 −2 Original line number Diff line number Diff line Loading @@ -127,15 +127,28 @@ public class LeAudioNativeInterface { sendMessageToService(event); } private void onGroupNodeStatus(byte[] address, int groupId, int nodeStatus) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); event.valueInt1 = groupId; event.valueInt2 = nodeStatus; event.device = getDevice(address); if (DBG) { Log.d(TAG, "onGroupNodeStatus: " + event); } sendMessageToService(event); } private void onAudioConf(int direction, int groupId, int sinkAudioLocation, int sourceAudioLocation, byte[] address) { int sourceAudioLocation, int availableContexts) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); event.valueInt1 = direction; event.valueInt2 = groupId; event.valueInt3 = sinkAudioLocation; event.valueInt4 = sourceAudioLocation; event.device = getDevice(address); event.valueInt5 = availableContexts; if (DBG) { Log.d(TAG, "onAudioConf: " + event); Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +311 −23 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.bluetooth.le_audio; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import android.annotation.RequiresPermission; Loading Loading @@ -64,20 +66,52 @@ public class LeAudioService extends ProfileService { private static final int MAX_LE_AUDIO_STATE_MACHINES = 10; private static LeAudioService sLeAudioService; /** * Indicates group audio support for input direction */ private static final int AUDIO_DIRECTION_INPUT_BIT = 0x01; /** * Indicates group audio support for output direction */ private static final int AUDIO_DIRECTION_OUTPUT_BIT = 0x02; /* * Indicates no active contexts */ private static final int ACTIVE_CONTEXTS_NONE = 0; private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; private BluetoothDevice mPreviousAudioDevice; private BluetoothDevice mPreviousAudioOutDevice; private BluetoothDevice mPreviousAudioInDevice; ServiceFactory mServiceFactory = new ServiceFactory(); LeAudioNativeInterface mLeAudioNativeInterface; AudioManager mAudioManager; private class LeAudioGroupDescriptor { LeAudioGroupDescriptor() { mIsConnected = false; mIsActive = false; mActiveContexts = ACTIVE_CONTEXTS_NONE; } public Boolean mIsConnected; public Boolean mIsActive; public Integer mActiveContexts; } private final Map<Integer, LeAudioGroupDescriptor> mGroupDescriptors = new HashMap<>(); private final Map<BluetoothDevice, LeAudioStateMachine> mStateMachines = new HashMap<>(); private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>(); private final Map<Integer, Boolean> mGroupIdConnectedMap = new HashMap<>(); private int mActiveDeviceGroupId = LE_AUDIO_GROUP_ID_INVALID; private final int mContextSupportingInputAudio = BluetoothLeAudio.CONTEXT_TYPE_COMMUNICATION; private final int mContextSupportingOutputAudio = BluetoothLeAudio.CONTEXT_TYPE_COMMUNICATION | BluetoothLeAudio.CONTEXT_TYPE_MEDIA; private BroadcastReceiver mBondStateChangedReceiver; private BroadcastReceiver mConnectionStateChangedReceiver; Loading Loading @@ -115,7 +149,7 @@ public class LeAudioService extends ProfileService { mStateMachinesThread.start(); mDeviceGroupIdMap.clear(); mGroupIdConnectedMap.clear(); mGroupDescriptors.clear(); // Setup broadcast receivers IntentFilter filter = new IntentFilter(); Loading Loading @@ -166,7 +200,7 @@ public class LeAudioService extends ProfileService { } mDeviceGroupIdMap.clear(); mGroupIdConnectedMap.clear(); mGroupDescriptors.clear(); if (mStateMachinesThread != null) { mStateMachinesThread.quitSafely(); Loading Loading @@ -300,6 +334,9 @@ public class LeAudioService extends ProfileService { } } } mGroupDescriptors.remove(groupId); return true; } Loading Loading @@ -382,18 +419,175 @@ public class LeAudioService extends ProfileService { } /** * Set the active device. * Get supported group audio direction from available context. * * @param activeContext 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 */ private Integer getAudioDirectionsFromActiveContextsMap(Integer activeContexts) { Integer supportedAudioDirections = 0; if ((activeContexts & mContextSupportingInputAudio) != 0) { supportedAudioDirections |= AUDIO_DIRECTION_INPUT_BIT; } if ((activeContexts & mContextSupportingOutputAudio) != 0) { supportedAudioDirections |= AUDIO_DIRECTION_OUTPUT_BIT; } return supportedAudioDirections; } private BluetoothDevice getFirstConnectedDeviceFromGroup(Integer groupId) { if (groupId != LE_AUDIO_GROUP_ID_INVALID) { for(Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) { if ((entry.getValue() == groupId) && (entry.getKey().isConnected())) { return entry.getKey(); } } } return null; } /** * Report the active devices change to the active device manager and the media framework. * @param groupId id of group which devices should be updated * @param newActiveContexts new active contexts for group of devices */ private void updateActiveDevices(Integer groupId, Integer oldActiveContexts, Integer newActiveContexts) { LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId); if (descriptor == null) { Log.w(TAG, "Invalid group id: " + String.valueOf(groupId)); return; } BluetoothDevice device = getFirstConnectedDeviceFromGroup(groupId); Integer oldSupportedAudioDirections = getAudioDirectionsFromActiveContextsMap(oldActiveContexts); boolean oldSupportedByDeviceOutput = (oldSupportedAudioDirections & AUDIO_DIRECTION_OUTPUT_BIT) != 0; boolean oldSupportedByDeviceInput = (oldSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0; Integer newSupportedAudioDirections = getAudioDirectionsFromActiveContextsMap(newActiveContexts); boolean newSupportedByDeviceOutput = (newSupportedAudioDirections & AUDIO_DIRECTION_OUTPUT_BIT) != 0; boolean newSupportedByDeviceInput = (newSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0; /* Disconnect output: * - If active output device changed (to none or any) * - If device stops supporting output */ boolean outActiveDeviceReplace = (mPreviousAudioOutDevice != null) && !(device == mPreviousAudioOutDevice); if (outActiveDeviceReplace || (oldSupportedByDeviceOutput && !newSupportedByDeviceOutput)) { boolean suppressNoisyIntent = (getConnectionState(mPreviousAudioOutDevice) == BluetoothProfile.STATE_CONNECTED); mAudioManager.setBluetoothLeAudioOutDeviceConnectionState( mPreviousAudioOutDevice, BluetoothProfile.STATE_DISCONNECTED, suppressNoisyIntent); mPreviousAudioOutDevice = null; } /* Connect output: * - If active output device changed * - If device starts support output */ if ((outActiveDeviceReplace && (device != null)) || (!oldSupportedByDeviceOutput && newSupportedByDeviceOutput)) { mAudioManager.setBluetoothLeAudioOutDeviceConnectionState( device, BluetoothProfile.STATE_CONNECTED, true); mPreviousAudioOutDevice = device; } /* Disconnect input: * - If active input device changed (to none or any) * - If device stops supporting input */ boolean inActiveDeviceReplace = (mPreviousAudioInDevice != null) && !(device == mPreviousAudioInDevice); if (inActiveDeviceReplace || (oldSupportedByDeviceInput && !newSupportedByDeviceInput)) { mAudioManager.setBluetoothLeAudioInDeviceConnectionState( mPreviousAudioInDevice, BluetoothProfile.STATE_DISCONNECTED); mPreviousAudioInDevice = null; } /* Connect input: * - If active input device changed * - If device starts support input */ if ((inActiveDeviceReplace && (device != null)) || (!oldSupportedByDeviceInput && newSupportedByDeviceInput)) { mAudioManager.setBluetoothLeAudioInDeviceConnectionState( device, BluetoothProfile.STATE_CONNECTED); mPreviousAudioInDevice = device; } Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); if ((mPreviousAudioInDevice == null) && (mPreviousAudioOutDevice == null)) { /* Device has to be unabigous, represented by empty BluetoothDevice */ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mPreviousAudioOutDevice); } else { intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); sendBroadcast(intent, BLUETOOTH_CONNECT); } /** * Set the active device group. * @param groupId group Id to set active */ private void setActiveDeviceGroup(BluetoothDevice device) { int groupId = LE_AUDIO_GROUP_ID_INVALID; if (device != null) { groupId = mDeviceGroupIdMap.getOrDefault(device, LE_AUDIO_GROUP_ID_INVALID); } if (DBG) { Log.d(TAG, "setActiveDeviceGroup = " + groupId + ", device: " + device); } if (groupId == mActiveDeviceGroupId) { Log.w(TAG, "group is already active"); return; } mActiveDeviceGroupId = groupId; } /** * Set the active group represented by device. * * @param device the new active device * @return true on success, otherwise false */ public boolean setActiveDevice(BluetoothDevice device) { if (DBG) { Log.d(TAG, "setActiveDevice:" + 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); return true; } } /** * Get the connected physical LeAudio devices that are active. Loading @@ -405,6 +599,19 @@ public class LeAudioService extends ProfileService { Log.d(TAG, "getActiveDevices"); } ArrayList<BluetoothDevice> activeDevices = new ArrayList<>(); synchronized (mStateMachines) { if (mActiveDeviceGroupId == LE_AUDIO_GROUP_ID_INVALID) { return activeDevices; } for (BluetoothDevice device : mDeviceGroupIdMap.keySet()) { if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { continue; } if (mDeviceGroupIdMap.get(device) == Integer.valueOf(mActiveDeviceGroupId)) { activeDevices.add(device); } } } return activeDevices; } Loading @@ -413,6 +620,7 @@ public class LeAudioService extends ProfileService { void messageFromNative(LeAudioStackEvent stackEvent) { Log.d(TAG, "Message from native: " + stackEvent); BluetoothDevice device = stackEvent.device; Intent intent = null; if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { // Some events require device state machine Loading @@ -437,32 +645,98 @@ public class LeAudioService extends ProfileService { sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent); return; } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED) { int group_id = stackEvent.valueInt1; int node_status = stackEvent.valueInt2; Objects.requireNonNull(stackEvent.device, "Device should never be null, event: " + stackEvent); switch (node_status) { case LeAudioStackEvent.GROUP_NODE_ADDED: mDeviceGroupIdMap.put(device, group_id); mGroupDescriptors.put(group_id, new LeAudioGroupDescriptor()); break; case LeAudioStackEvent.GROUP_NODE_REMOVED: mDeviceGroupIdMap.remove(device); mGroupDescriptors.remove(group_id); break; default: break; } // Some events do not require device state machine if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED) { intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, group_id); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_NODE_STATUS, node_status); } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED) { int direction = stackEvent.valueInt1; int group_id = 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); if (descriptor != null) { if (descriptor.mIsActive) { updateActiveDevices(group_id, descriptor.mActiveContexts, available_contexts); } descriptor.mActiveContexts = available_contexts; } else { Log.e(TAG, "no descriptors for group: " + group_id); } intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONF_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, group_id); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_DIRECTION, direction); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_SINK_LOCATION, snk_audio_location); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_SOURCE_LOCATION, src_audio_location); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS, available_contexts); } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED) { int group_id = stackEvent.valueInt1; int group_status = stackEvent.valueInt2; int group_flags = stackEvent.valueInt3; // TODO: Handle Stream events switch (group_status) { case LeAudioStackEvent.GROUP_STATUS_IDLE: case LeAudioStackEvent.GROUP_STATUS_RECONFIGURED: case LeAudioStackEvent.GROUP_STATUS_DESTROYED: case LeAudioStackEvent.GROUP_STATUS_SUSPENDED: setActiveDevice(null); // TODO: Get all devices with a group and Unassign? or they are already unasigned // This event may come after removing all the nodes from a certain group but only that. break; case LeAudioStackEvent.GROUP_STATUS_STREAMING: BluetoothDevice streaming_device = getConnectedPeerDevices(group_id).get(0); setActiveDevice(streaming_device); break; case LeAudioStackEvent.GROUP_STATUS_ACTIVE: { LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(group_id); if (descriptor != null) { if (!descriptor.mIsActive) { updateActiveDevices(group_id, ACTIVE_CONTEXTS_NONE, descriptor.mActiveContexts); descriptor.mIsActive = true; } } else { Log.e(TAG, "no descriptors for group: " + group_id); } break; } case LeAudioStackEvent.GROUP_STATUS_INACTIVE: { LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(group_id); if (descriptor != null) { if (descriptor.mIsActive) { updateActiveDevices(group_id, descriptor.mActiveContexts, ACTIVE_CONTEXTS_NONE); descriptor.mIsActive = false; } } else { Log.e(TAG, "no descriptors for group: " + group_id); } break; } default: break; } intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_STATUS_CHANGED); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, group_id); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_STATUS, group_status); } } Loading Loading @@ -580,8 +854,16 @@ public class LeAudioService extends ProfileService { // MetricsLogger.logProfileConnectionEvent( // BluetoothMetricsProto.ProfileId.LE_AUDIO); } if (!mGroupIdConnectedMap.getOrDefault(myGroupId, false)) { mGroupIdConnectedMap.put(myGroupId, true); LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(myGroupId); if (descriptor != null) { descriptor.mIsConnected = true; /* HearingAid activates device after connection * A2dp makes active device via activedevicemanager - connection intent */ setActiveDevice(device); } else { Log.e(TAG, "no descriptors for group: " + myGroupId); } McpService mcpService = mServiceFactory.getMcpService(); Loading @@ -592,7 +874,13 @@ public class LeAudioService extends ProfileService { if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) { setActiveDevice(null); int myGroupId = getGroupId(device); mGroupIdConnectedMap.put(myGroupId, false); LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(myGroupId); if (descriptor != null) { descriptor.mIsConnected = true; } else { Log.e(TAG, "no descriptors for group: " + myGroupId); } } // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { Loading android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java +39 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
android/app/jni/com_android_bluetooth_le_audio.cpp +8 −16 Original line number Diff line number Diff line Loading @@ -82,27 +82,19 @@ class LeAudioClientCallbacksImpl : public LeAudioClientCallbacks { (jint)group_flags); } void OnAudioConf(const RawAddress& bd_addr, uint8_t direction, uint8_t group_id, uint32_t sink_audio_location, uint32_t source_audio_location) override { void OnAudioConf(uint8_t direction, int group_id, uint32_t sink_audio_location, uint32_t source_audio_location, uint16_t avail_cont) override { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; ScopedLocalRef<jbyteArray> addr( sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); if (!addr.get()) { LOG(ERROR) << "Failed to new jbyteArray bd addr for group status"; return; } sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), (jbyte*)&bd_addr); sCallbackEnv->CallVoidMethod( mCallbacksObj, method_onAudioConf, (jint)direction, (jint)group_id, (jint)sink_audio_location, (jint)source_audio_location, addr.get()); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioConf, (jint)direction, (jint)group_id, (jint)sink_audio_location, (jint)source_audio_location, (jint)avail_cont); } void OnSetMemberAvailable(const RawAddress& bd_addr, Loading Loading @@ -131,7 +123,7 @@ static LeAudioClientCallbacksImpl sLeAudioClientCallbacks; static void classInitNative(JNIEnv* env, jclass clazz) { method_onGroupStatus = env->GetMethodID(clazz, "onGroupStatus", "(III)V"); method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIII[B)V"); method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIIII)V"); method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); method_onSetMemberAvailable = Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java +15 −2 Original line number Diff line number Diff line Loading @@ -127,15 +127,28 @@ public class LeAudioNativeInterface { sendMessageToService(event); } private void onGroupNodeStatus(byte[] address, int groupId, int nodeStatus) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); event.valueInt1 = groupId; event.valueInt2 = nodeStatus; event.device = getDevice(address); if (DBG) { Log.d(TAG, "onGroupNodeStatus: " + event); } sendMessageToService(event); } private void onAudioConf(int direction, int groupId, int sinkAudioLocation, int sourceAudioLocation, byte[] address) { int sourceAudioLocation, int availableContexts) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); event.valueInt1 = direction; event.valueInt2 = groupId; event.valueInt3 = sinkAudioLocation; event.valueInt4 = sourceAudioLocation; event.device = getDevice(address); event.valueInt5 = availableContexts; if (DBG) { Log.d(TAG, "onAudioConf: " + event); Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +311 −23 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.bluetooth.le_audio; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import android.annotation.RequiresPermission; Loading Loading @@ -64,20 +66,52 @@ public class LeAudioService extends ProfileService { private static final int MAX_LE_AUDIO_STATE_MACHINES = 10; private static LeAudioService sLeAudioService; /** * Indicates group audio support for input direction */ private static final int AUDIO_DIRECTION_INPUT_BIT = 0x01; /** * Indicates group audio support for output direction */ private static final int AUDIO_DIRECTION_OUTPUT_BIT = 0x02; /* * Indicates no active contexts */ private static final int ACTIVE_CONTEXTS_NONE = 0; private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; private BluetoothDevice mPreviousAudioDevice; private BluetoothDevice mPreviousAudioOutDevice; private BluetoothDevice mPreviousAudioInDevice; ServiceFactory mServiceFactory = new ServiceFactory(); LeAudioNativeInterface mLeAudioNativeInterface; AudioManager mAudioManager; private class LeAudioGroupDescriptor { LeAudioGroupDescriptor() { mIsConnected = false; mIsActive = false; mActiveContexts = ACTIVE_CONTEXTS_NONE; } public Boolean mIsConnected; public Boolean mIsActive; public Integer mActiveContexts; } private final Map<Integer, LeAudioGroupDescriptor> mGroupDescriptors = new HashMap<>(); private final Map<BluetoothDevice, LeAudioStateMachine> mStateMachines = new HashMap<>(); private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>(); private final Map<Integer, Boolean> mGroupIdConnectedMap = new HashMap<>(); private int mActiveDeviceGroupId = LE_AUDIO_GROUP_ID_INVALID; private final int mContextSupportingInputAudio = BluetoothLeAudio.CONTEXT_TYPE_COMMUNICATION; private final int mContextSupportingOutputAudio = BluetoothLeAudio.CONTEXT_TYPE_COMMUNICATION | BluetoothLeAudio.CONTEXT_TYPE_MEDIA; private BroadcastReceiver mBondStateChangedReceiver; private BroadcastReceiver mConnectionStateChangedReceiver; Loading Loading @@ -115,7 +149,7 @@ public class LeAudioService extends ProfileService { mStateMachinesThread.start(); mDeviceGroupIdMap.clear(); mGroupIdConnectedMap.clear(); mGroupDescriptors.clear(); // Setup broadcast receivers IntentFilter filter = new IntentFilter(); Loading Loading @@ -166,7 +200,7 @@ public class LeAudioService extends ProfileService { } mDeviceGroupIdMap.clear(); mGroupIdConnectedMap.clear(); mGroupDescriptors.clear(); if (mStateMachinesThread != null) { mStateMachinesThread.quitSafely(); Loading Loading @@ -300,6 +334,9 @@ public class LeAudioService extends ProfileService { } } } mGroupDescriptors.remove(groupId); return true; } Loading Loading @@ -382,18 +419,175 @@ public class LeAudioService extends ProfileService { } /** * Set the active device. * Get supported group audio direction from available context. * * @param activeContext 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 */ private Integer getAudioDirectionsFromActiveContextsMap(Integer activeContexts) { Integer supportedAudioDirections = 0; if ((activeContexts & mContextSupportingInputAudio) != 0) { supportedAudioDirections |= AUDIO_DIRECTION_INPUT_BIT; } if ((activeContexts & mContextSupportingOutputAudio) != 0) { supportedAudioDirections |= AUDIO_DIRECTION_OUTPUT_BIT; } return supportedAudioDirections; } private BluetoothDevice getFirstConnectedDeviceFromGroup(Integer groupId) { if (groupId != LE_AUDIO_GROUP_ID_INVALID) { for(Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) { if ((entry.getValue() == groupId) && (entry.getKey().isConnected())) { return entry.getKey(); } } } return null; } /** * Report the active devices change to the active device manager and the media framework. * @param groupId id of group which devices should be updated * @param newActiveContexts new active contexts for group of devices */ private void updateActiveDevices(Integer groupId, Integer oldActiveContexts, Integer newActiveContexts) { LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId); if (descriptor == null) { Log.w(TAG, "Invalid group id: " + String.valueOf(groupId)); return; } BluetoothDevice device = getFirstConnectedDeviceFromGroup(groupId); Integer oldSupportedAudioDirections = getAudioDirectionsFromActiveContextsMap(oldActiveContexts); boolean oldSupportedByDeviceOutput = (oldSupportedAudioDirections & AUDIO_DIRECTION_OUTPUT_BIT) != 0; boolean oldSupportedByDeviceInput = (oldSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0; Integer newSupportedAudioDirections = getAudioDirectionsFromActiveContextsMap(newActiveContexts); boolean newSupportedByDeviceOutput = (newSupportedAudioDirections & AUDIO_DIRECTION_OUTPUT_BIT) != 0; boolean newSupportedByDeviceInput = (newSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0; /* Disconnect output: * - If active output device changed (to none or any) * - If device stops supporting output */ boolean outActiveDeviceReplace = (mPreviousAudioOutDevice != null) && !(device == mPreviousAudioOutDevice); if (outActiveDeviceReplace || (oldSupportedByDeviceOutput && !newSupportedByDeviceOutput)) { boolean suppressNoisyIntent = (getConnectionState(mPreviousAudioOutDevice) == BluetoothProfile.STATE_CONNECTED); mAudioManager.setBluetoothLeAudioOutDeviceConnectionState( mPreviousAudioOutDevice, BluetoothProfile.STATE_DISCONNECTED, suppressNoisyIntent); mPreviousAudioOutDevice = null; } /* Connect output: * - If active output device changed * - If device starts support output */ if ((outActiveDeviceReplace && (device != null)) || (!oldSupportedByDeviceOutput && newSupportedByDeviceOutput)) { mAudioManager.setBluetoothLeAudioOutDeviceConnectionState( device, BluetoothProfile.STATE_CONNECTED, true); mPreviousAudioOutDevice = device; } /* Disconnect input: * - If active input device changed (to none or any) * - If device stops supporting input */ boolean inActiveDeviceReplace = (mPreviousAudioInDevice != null) && !(device == mPreviousAudioInDevice); if (inActiveDeviceReplace || (oldSupportedByDeviceInput && !newSupportedByDeviceInput)) { mAudioManager.setBluetoothLeAudioInDeviceConnectionState( mPreviousAudioInDevice, BluetoothProfile.STATE_DISCONNECTED); mPreviousAudioInDevice = null; } /* Connect input: * - If active input device changed * - If device starts support input */ if ((inActiveDeviceReplace && (device != null)) || (!oldSupportedByDeviceInput && newSupportedByDeviceInput)) { mAudioManager.setBluetoothLeAudioInDeviceConnectionState( device, BluetoothProfile.STATE_CONNECTED); mPreviousAudioInDevice = device; } Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); if ((mPreviousAudioInDevice == null) && (mPreviousAudioOutDevice == null)) { /* Device has to be unabigous, represented by empty BluetoothDevice */ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mPreviousAudioOutDevice); } else { intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); sendBroadcast(intent, BLUETOOTH_CONNECT); } /** * Set the active device group. * @param groupId group Id to set active */ private void setActiveDeviceGroup(BluetoothDevice device) { int groupId = LE_AUDIO_GROUP_ID_INVALID; if (device != null) { groupId = mDeviceGroupIdMap.getOrDefault(device, LE_AUDIO_GROUP_ID_INVALID); } if (DBG) { Log.d(TAG, "setActiveDeviceGroup = " + groupId + ", device: " + device); } if (groupId == mActiveDeviceGroupId) { Log.w(TAG, "group is already active"); return; } mActiveDeviceGroupId = groupId; } /** * Set the active group represented by device. * * @param device the new active device * @return true on success, otherwise false */ public boolean setActiveDevice(BluetoothDevice device) { if (DBG) { Log.d(TAG, "setActiveDevice:" + 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); return true; } } /** * Get the connected physical LeAudio devices that are active. Loading @@ -405,6 +599,19 @@ public class LeAudioService extends ProfileService { Log.d(TAG, "getActiveDevices"); } ArrayList<BluetoothDevice> activeDevices = new ArrayList<>(); synchronized (mStateMachines) { if (mActiveDeviceGroupId == LE_AUDIO_GROUP_ID_INVALID) { return activeDevices; } for (BluetoothDevice device : mDeviceGroupIdMap.keySet()) { if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { continue; } if (mDeviceGroupIdMap.get(device) == Integer.valueOf(mActiveDeviceGroupId)) { activeDevices.add(device); } } } return activeDevices; } Loading @@ -413,6 +620,7 @@ public class LeAudioService extends ProfileService { void messageFromNative(LeAudioStackEvent stackEvent) { Log.d(TAG, "Message from native: " + stackEvent); BluetoothDevice device = stackEvent.device; Intent intent = null; if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { // Some events require device state machine Loading @@ -437,32 +645,98 @@ public class LeAudioService extends ProfileService { sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent); return; } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED) { int group_id = stackEvent.valueInt1; int node_status = stackEvent.valueInt2; Objects.requireNonNull(stackEvent.device, "Device should never be null, event: " + stackEvent); switch (node_status) { case LeAudioStackEvent.GROUP_NODE_ADDED: mDeviceGroupIdMap.put(device, group_id); mGroupDescriptors.put(group_id, new LeAudioGroupDescriptor()); break; case LeAudioStackEvent.GROUP_NODE_REMOVED: mDeviceGroupIdMap.remove(device); mGroupDescriptors.remove(group_id); break; default: break; } // Some events do not require device state machine if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED) { intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, group_id); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_NODE_STATUS, node_status); } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED) { int direction = stackEvent.valueInt1; int group_id = 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); if (descriptor != null) { if (descriptor.mIsActive) { updateActiveDevices(group_id, descriptor.mActiveContexts, available_contexts); } descriptor.mActiveContexts = available_contexts; } else { Log.e(TAG, "no descriptors for group: " + group_id); } intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONF_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, group_id); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_DIRECTION, direction); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_SINK_LOCATION, snk_audio_location); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_SOURCE_LOCATION, src_audio_location); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS, available_contexts); } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED) { int group_id = stackEvent.valueInt1; int group_status = stackEvent.valueInt2; int group_flags = stackEvent.valueInt3; // TODO: Handle Stream events switch (group_status) { case LeAudioStackEvent.GROUP_STATUS_IDLE: case LeAudioStackEvent.GROUP_STATUS_RECONFIGURED: case LeAudioStackEvent.GROUP_STATUS_DESTROYED: case LeAudioStackEvent.GROUP_STATUS_SUSPENDED: setActiveDevice(null); // TODO: Get all devices with a group and Unassign? or they are already unasigned // This event may come after removing all the nodes from a certain group but only that. break; case LeAudioStackEvent.GROUP_STATUS_STREAMING: BluetoothDevice streaming_device = getConnectedPeerDevices(group_id).get(0); setActiveDevice(streaming_device); break; case LeAudioStackEvent.GROUP_STATUS_ACTIVE: { LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(group_id); if (descriptor != null) { if (!descriptor.mIsActive) { updateActiveDevices(group_id, ACTIVE_CONTEXTS_NONE, descriptor.mActiveContexts); descriptor.mIsActive = true; } } else { Log.e(TAG, "no descriptors for group: " + group_id); } break; } case LeAudioStackEvent.GROUP_STATUS_INACTIVE: { LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(group_id); if (descriptor != null) { if (descriptor.mIsActive) { updateActiveDevices(group_id, descriptor.mActiveContexts, ACTIVE_CONTEXTS_NONE); descriptor.mIsActive = false; } } else { Log.e(TAG, "no descriptors for group: " + group_id); } break; } default: break; } intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_STATUS_CHANGED); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, group_id); intent.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_STATUS, group_status); } } Loading Loading @@ -580,8 +854,16 @@ public class LeAudioService extends ProfileService { // MetricsLogger.logProfileConnectionEvent( // BluetoothMetricsProto.ProfileId.LE_AUDIO); } if (!mGroupIdConnectedMap.getOrDefault(myGroupId, false)) { mGroupIdConnectedMap.put(myGroupId, true); LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(myGroupId); if (descriptor != null) { descriptor.mIsConnected = true; /* HearingAid activates device after connection * A2dp makes active device via activedevicemanager - connection intent */ setActiveDevice(device); } else { Log.e(TAG, "no descriptors for group: " + myGroupId); } McpService mcpService = mServiceFactory.getMcpService(); Loading @@ -592,7 +874,13 @@ public class LeAudioService extends ProfileService { if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) { setActiveDevice(null); int myGroupId = getGroupId(device); mGroupIdConnectedMap.put(myGroupId, false); LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(myGroupId); if (descriptor != null) { descriptor.mIsConnected = true; } else { Log.e(TAG, "no descriptors for group: " + myGroupId); } } // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java +39 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes