Loading AndroidTestTemplate.xml +11 −4 Original line number Original line Diff line number Diff line Loading @@ -17,9 +17,10 @@ <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct-native" /> <option name="test-suite-tag" value="apct-native" /> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> <option name="cleanup" value="true" /> <option name="cleanup" value="true" /> <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" /> <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" /> <option name="append-bitness" value="true" /> </target_preparer> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="settings put global ble_scan_always_enabled 0" /> <option name="run-command" value="settings put global ble_scan_always_enabled 0" /> Loading @@ -33,4 +34,10 @@ <option name="module-name" value="{MODULE}" /> <option name="module-name" value="{MODULE}" /> <option name="run-test-as" value="0" /> <option name="run-test-as" value="0" /> </test> </test> <!-- Only run tests in MTS if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> <option name="mainline-module-package-name" value="com.google.android.bluetooth" /> </object> </configuration> </configuration> android/app/src/com/android/bluetooth/btservice/AdapterService.java +8 −8 Original line number Original line Diff line number Diff line Loading @@ -148,13 +148,14 @@ import java.time.Duration; import java.util.ArrayDeque; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashMap; import java.util.HashSet; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.List; import java.util.Map; import java.util.Map; import java.util.Set; import java.util.Set; import java.util.UUID; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executor; import java.util.function.Predicate; import java.util.function.Predicate; Loading Loading @@ -282,8 +283,7 @@ public class AdapterService extends Service { private boolean mQuietmode = false; private boolean mQuietmode = false; private HashMap<String, CallerInfo> mBondAttemptCallerInfo = new HashMap<>(); private HashMap<String, CallerInfo> mBondAttemptCallerInfo = new HashMap<>(); private final Map<UUID, RfcommListenerData> mBluetoothServerSockets = private final Map<UUID, RfcommListenerData> mBluetoothServerSockets = new ConcurrentHashMap<>(); Collections.synchronizedMap(new HashMap<>()); private final Executor mSocketServersExecutor = r -> new Thread(r).start(); private final Executor mSocketServersExecutor = r -> new Thread(r).start(); private AlarmManager mAlarmManager; private AlarmManager mAlarmManager; Loading Loading @@ -1491,11 +1491,11 @@ public class AdapterService extends Service { } } private void stopRfcommServerSockets() { private void stopRfcommServerSockets() { synchronized (mBluetoothServerSockets) { Iterator<Map.Entry<UUID, RfcommListenerData>> socketsIterator = mBluetoothServerSockets.forEach((key, value) -> { mBluetoothServerSockets.entrySet().iterator(); mBluetoothServerSockets.remove(key); while (socketsIterator.hasNext()) { value.closeServerAndPendingSockets(mHandler); socketsIterator.next().getValue().closeServerAndPendingSockets(mHandler); }); socketsIterator.remove(); } } } } Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +26 −13 Original line number Original line Diff line number Diff line Loading @@ -139,7 +139,7 @@ public class LeAudioService extends ProfileService { mIsActive = false; mIsActive = false; mActiveContexts = ACTIVE_CONTEXTS_NONE; mActiveContexts = ACTIVE_CONTEXTS_NONE; mCodecStatus = null; mCodecStatus = null; mLostDevicesWhileStreaming = new ArrayList<>(); mLostLeadDeviceWhileStreaming = null; } } public Boolean mIsConnected; public Boolean mIsConnected; Loading @@ -147,7 +147,7 @@ public class LeAudioService extends ProfileService { public Integer mActiveContexts; public Integer mActiveContexts; public BluetoothLeAudioCodecStatus mCodecStatus; public BluetoothLeAudioCodecStatus mCodecStatus; /* This can be non empty only for the streaming time */ /* This can be non empty only for the streaming time */ List<BluetoothDevice> mLostDevicesWhileStreaming; BluetoothDevice mLostLeadDeviceWhileStreaming; } } List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>(); List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>(); Loading Loading @@ -1066,18 +1066,19 @@ public class LeAudioService extends ProfileService { } } private void clearLostDevicesWhileStreaming(LeAudioGroupDescriptor descriptor) { private void clearLostDevicesWhileStreaming(LeAudioGroupDescriptor descriptor) { for (BluetoothDevice dev : descriptor.mLostDevicesWhileStreaming) { if (DBG) { LeAudioStateMachine sm = mStateMachines.get(dev); Log.d(TAG, " lost dev: " + descriptor.mLostLeadDeviceWhileStreaming); if (sm == null) { continue; } } LeAudioStateMachine sm = mStateMachines.get(descriptor.mLostLeadDeviceWhileStreaming); if (sm != null) { LeAudioStackEvent stackEvent = LeAudioStackEvent stackEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); stackEvent.device = dev; stackEvent.device = descriptor.mLostLeadDeviceWhileStreaming; stackEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED; stackEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED; sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent); sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent); } } descriptor.mLostLeadDeviceWhileStreaming = null; } } // Suppressed since this is part of a local process // Suppressed since this is part of a local process Loading Loading @@ -1110,14 +1111,17 @@ public class LeAudioService extends ProfileService { mActiveAudioOutDevice) mActiveAudioOutDevice) || Objects.equals(device, mActiveAudioInDevice)) || Objects.equals(device, mActiveAudioInDevice)) && (getConnectedPeerDevices(groupId).size() > 1)) { && (getConnectedPeerDevices(groupId).size() > 1)) { descriptor.mLostDevicesWhileStreaming.add(device); if (DBG) Log.d(TAG, "Adding to lost devices : " + device); descriptor.mLostLeadDeviceWhileStreaming = device; return; return; } } break; break; case LeAudioStackEvent.CONNECTION_STATE_CONNECTED: case LeAudioStackEvent.CONNECTION_STATE_CONNECTED: case LeAudioStackEvent.CONNECTION_STATE_CONNECTING: case LeAudioStackEvent.CONNECTION_STATE_CONNECTING: if (descriptor != null) { if (descriptor != null) { descriptor.mLostDevicesWhileStreaming.remove(device); if (DBG) Log.d(TAG, "Removing from lost devices : " + device); descriptor.mLostLeadDeviceWhileStreaming = null; /* Try to connect other devices from the group */ /* Try to connect other devices from the group */ connectSet(device); connectSet(device); } } Loading Loading @@ -1254,6 +1258,7 @@ public class LeAudioService extends ProfileService { ACTIVE_CONTEXTS_NONE, descriptor.mIsActive); ACTIVE_CONTEXTS_NONE, descriptor.mIsActive); notifyGroupStatus = true; notifyGroupStatus = true; /* Clear lost devices */ /* Clear lost devices */ if (DBG) Log.d(TAG, "Clear for group: " + groupId); clearLostDevicesWhileStreaming(descriptor); clearLostDevicesWhileStreaming(descriptor); } } } else { } else { Loading Loading @@ -1538,6 +1543,15 @@ public class LeAudioService extends ProfileService { return; return; } } List<BluetoothDevice> connectedDevices = getConnectedPeerDevices(myGroupId); /* Let's check if the last connected device is really connected */ if (connectedDevices.size() == 1 && Objects.equals(connectedDevices.get(0), descriptor.mLostLeadDeviceWhileStreaming)) { clearLostDevicesWhileStreaming(descriptor); return; } if (getConnectedPeerDevices(myGroupId).isEmpty()){ if (getConnectedPeerDevices(myGroupId).isEmpty()){ descriptor.mIsConnected = false; descriptor.mIsConnected = false; if (descriptor.mIsActive) { if (descriptor.mIsActive) { Loading Loading @@ -2584,9 +2598,8 @@ public class LeAudioService extends ProfileService { ProfileService.println(sb, " mActiveContexts: " + descriptor.mActiveContexts); ProfileService.println(sb, " mActiveContexts: " + descriptor.mActiveContexts); ProfileService.println(sb, " group lead: " + getConnectedGroupLeadDevice(groupId)); ProfileService.println(sb, " group lead: " + getConnectedGroupLeadDevice(groupId)); ProfileService.println(sb, " first device: " + getFirstDeviceFromGroup(groupId)); ProfileService.println(sb, " first device: " + getFirstDeviceFromGroup(groupId)); for (BluetoothDevice dev : descriptor.mLostDevicesWhileStreaming) { ProfileService.println(sb, " lost lead device: " ProfileService.println(sb, " lost device: " + dev); + descriptor.mLostLeadDeviceWhileStreaming); } } } } } } } android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +161 −0 Original line number Original line Diff line number Diff line Loading @@ -26,7 +26,11 @@ import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice; Loading @@ -42,6 +46,7 @@ import android.content.Context; import android.content.Intent; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentFilter; import android.media.AudioManager; import android.media.AudioManager; import android.media.BluetoothProfileConnectionInfo; import android.os.ParcelUuid; import android.os.ParcelUuid; import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry; Loading @@ -68,6 +73,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; import java.util.HashSet; import java.util.HashSet; import java.util.List; import java.util.List; import java.util.Objects; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException; Loading Loading @@ -457,6 +463,23 @@ public class LeAudioServiceTest { .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } } private void injectNoVerifyDeviceConnected(BluetoothDevice device) { generateUnexpectedConnectionMessageFromNative(device, LeAudioStackEvent.CONNECTION_STATE_CONNECTED, LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED); } private void injectAndVerifyDeviceDisconnected(BluetoothDevice device) { generateConnectionMessageFromNative(device, LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED, LeAudioStackEvent.CONNECTION_STATE_CONNECTED); } private void injectNoVerifyDeviceDisconnected(BluetoothDevice device) { generateUnexpectedConnectionMessageFromNative(device, LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED, LeAudioStackEvent.CONNECTION_STATE_CONNECTED); } /** /** * Test that the outgoing connect/disconnect and audio switch is successful. * Test that the outgoing connect/disconnect and audio switch is successful. */ */ Loading Loading @@ -1220,4 +1243,142 @@ public class LeAudioServiceTest { onGroupCodecConfChangedCallbackCalled = false; onGroupCodecConfChangedCallbackCalled = false; mService.mLeAudioCallbacks.unregister(leAudioCallbacks); mService.mLeAudioCallbacks.unregister(leAudioCallbacks); } } private void verifyActiveDeviceStateIntent(int timeoutMs, BluetoothDevice device) { Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device)); assertThat(intent).isNotNull(); assertThat(intent.getAction()) .isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); } /** * Test native interface group status message handling */ @Test public void testLeadGroupDeviceDisconnects() { int groupId = 1; int direction = 1; int snkAudioLocation = 3; int srcAudioLocation = 4; int availableContexts = 5; int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; BluetoothDevice leadDevice; BluetoothDevice memberDevice = mLeftDevice; doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); leadDevice = mService.getConnectedGroupLeadDevice(groupId); if (Objects.equals(leadDevice, mLeftDevice)) { memberDevice = mRightDevice; } assertThat(mService.setActiveDevice(leadDevice)).isTrue(); //Add location support LeAudioStackEvent audioConfChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); audioConfChangedEvent.valueInt1 = direction; audioConfChangedEvent.valueInt2 = groupId; audioConfChangedEvent.valueInt3 = snkAudioLocation; audioConfChangedEvent.valueInt4 = srcAudioLocation; audioConfChangedEvent.valueInt5 = availableContexts; mService.messageFromNative(audioConfChangedEvent); //Set group and device as active LeAudioStackEvent groupStatusChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); groupStatusChangedEvent.valueInt1 = groupId; groupStatusChangedEvent.valueInt2 = groupStatus; mService.messageFromNative(groupStatusChangedEvent); assertThat(mService.getActiveDevices().contains(leadDevice)).isTrue(); verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(leadDevice), any(), any(BluetoothProfileConnectionInfo.class)); verifyActiveDeviceStateIntent(TIMEOUT_MS, leadDevice); injectNoVerifyDeviceDisconnected(leadDevice); // We should not change the audio device assertThat(mService.getConnectionState(leadDevice)) .isEqualTo(BluetoothProfile.STATE_CONNECTED); injectAndVerifyDeviceDisconnected(memberDevice); // Verify the connection state broadcast, and that we are in Connecting state verifyConnectionStateIntent(TIMEOUT_MS, leadDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(any(), eq(leadDevice), any(BluetoothProfileConnectionInfo.class)); } /** * Test native interface group status message handling */ @Test public void testLeadGroupDeviceReconnects() { int groupId = 1; int direction = 1; int snkAudioLocation = 3; int srcAudioLocation = 4; int availableContexts = 5; int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; BluetoothDevice leadDevice; BluetoothDevice memberDevice = mLeftDevice; doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); leadDevice = mService.getConnectedGroupLeadDevice(groupId); if (Objects.equals(leadDevice, mLeftDevice)) { memberDevice = mRightDevice; } assertThat(mService.setActiveDevice(leadDevice)).isTrue(); //Add location support LeAudioStackEvent audioConfChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); audioConfChangedEvent.valueInt1 = direction; audioConfChangedEvent.valueInt2 = groupId; audioConfChangedEvent.valueInt3 = snkAudioLocation; audioConfChangedEvent.valueInt4 = srcAudioLocation; audioConfChangedEvent.valueInt5 = availableContexts; mService.messageFromNative(audioConfChangedEvent); //Set group and device as active LeAudioStackEvent groupStatusChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); groupStatusChangedEvent.valueInt1 = groupId; groupStatusChangedEvent.valueInt2 = groupStatus; mService.messageFromNative(groupStatusChangedEvent); assertThat(mService.getActiveDevices().contains(leadDevice)).isTrue(); verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(leadDevice), any(), any(BluetoothProfileConnectionInfo.class)); verifyActiveDeviceStateIntent(TIMEOUT_MS, leadDevice); /* We don't want to distribute DISCONNECTION event, instead will try to reconnect * (in native) */ injectNoVerifyDeviceDisconnected(leadDevice); assertThat(mService.getConnectionState(leadDevice)) .isEqualTo(BluetoothProfile.STATE_CONNECTED); /* Reconnect device, there should be no intent about that, as device was pretending * connected */ injectNoVerifyDeviceConnected(leadDevice); injectAndVerifyDeviceDisconnected(memberDevice); injectAndVerifyDeviceDisconnected(leadDevice); verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), eq(leadDevice), any(BluetoothProfileConnectionInfo.class)); } } } system/bta/csis/csis_client.cc +9 −3 Original line number Original line Diff line number Diff line Loading @@ -1558,9 +1558,15 @@ class CsisClientImpl : public CsisClient { OnGattNotification(p_data->notify); OnGattNotification(p_data->notify); break; break; case BTA_GATTC_ENC_CMPL_CB_EVT: case BTA_GATTC_ENC_CMPL_CB_EVT: { OnLeEncryptionComplete(p_data->enc_cmpl.remote_bda, BTM_SUCCESS); uint8_t encryption_status; break; if (BTM_IsEncrypted(p_data->enc_cmpl.remote_bda, BT_TRANSPORT_LE)) { encryption_status = BTM_SUCCESS; } else { encryption_status = BTM_FAILED_ON_SECURITY; } OnLeEncryptionComplete(p_data->enc_cmpl.remote_bda, encryption_status); } break; case BTA_GATTC_SRVC_CHG_EVT: case BTA_GATTC_SRVC_CHG_EVT: OnGattServiceChangeEvent(p_data->remote_bda); OnGattServiceChangeEvent(p_data->remote_bda); Loading Loading
AndroidTestTemplate.xml +11 −4 Original line number Original line Diff line number Diff line Loading @@ -17,9 +17,10 @@ <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct-native" /> <option name="test-suite-tag" value="apct-native" /> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> <option name="cleanup" value="true" /> <option name="cleanup" value="true" /> <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" /> <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" /> <option name="append-bitness" value="true" /> </target_preparer> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="settings put global ble_scan_always_enabled 0" /> <option name="run-command" value="settings put global ble_scan_always_enabled 0" /> Loading @@ -33,4 +34,10 @@ <option name="module-name" value="{MODULE}" /> <option name="module-name" value="{MODULE}" /> <option name="run-test-as" value="0" /> <option name="run-test-as" value="0" /> </test> </test> <!-- Only run tests in MTS if the Bluetooth Mainline module is installed. --> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> <option name="mainline-module-package-name" value="com.google.android.bluetooth" /> </object> </configuration> </configuration>
android/app/src/com/android/bluetooth/btservice/AdapterService.java +8 −8 Original line number Original line Diff line number Diff line Loading @@ -148,13 +148,14 @@ import java.time.Duration; import java.util.ArrayDeque; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashMap; import java.util.HashSet; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.List; import java.util.Map; import java.util.Map; import java.util.Set; import java.util.Set; import java.util.UUID; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executor; import java.util.function.Predicate; import java.util.function.Predicate; Loading Loading @@ -282,8 +283,7 @@ public class AdapterService extends Service { private boolean mQuietmode = false; private boolean mQuietmode = false; private HashMap<String, CallerInfo> mBondAttemptCallerInfo = new HashMap<>(); private HashMap<String, CallerInfo> mBondAttemptCallerInfo = new HashMap<>(); private final Map<UUID, RfcommListenerData> mBluetoothServerSockets = private final Map<UUID, RfcommListenerData> mBluetoothServerSockets = new ConcurrentHashMap<>(); Collections.synchronizedMap(new HashMap<>()); private final Executor mSocketServersExecutor = r -> new Thread(r).start(); private final Executor mSocketServersExecutor = r -> new Thread(r).start(); private AlarmManager mAlarmManager; private AlarmManager mAlarmManager; Loading Loading @@ -1491,11 +1491,11 @@ public class AdapterService extends Service { } } private void stopRfcommServerSockets() { private void stopRfcommServerSockets() { synchronized (mBluetoothServerSockets) { Iterator<Map.Entry<UUID, RfcommListenerData>> socketsIterator = mBluetoothServerSockets.forEach((key, value) -> { mBluetoothServerSockets.entrySet().iterator(); mBluetoothServerSockets.remove(key); while (socketsIterator.hasNext()) { value.closeServerAndPendingSockets(mHandler); socketsIterator.next().getValue().closeServerAndPendingSockets(mHandler); }); socketsIterator.remove(); } } } } Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +26 −13 Original line number Original line Diff line number Diff line Loading @@ -139,7 +139,7 @@ public class LeAudioService extends ProfileService { mIsActive = false; mIsActive = false; mActiveContexts = ACTIVE_CONTEXTS_NONE; mActiveContexts = ACTIVE_CONTEXTS_NONE; mCodecStatus = null; mCodecStatus = null; mLostDevicesWhileStreaming = new ArrayList<>(); mLostLeadDeviceWhileStreaming = null; } } public Boolean mIsConnected; public Boolean mIsConnected; Loading @@ -147,7 +147,7 @@ public class LeAudioService extends ProfileService { public Integer mActiveContexts; public Integer mActiveContexts; public BluetoothLeAudioCodecStatus mCodecStatus; public BluetoothLeAudioCodecStatus mCodecStatus; /* This can be non empty only for the streaming time */ /* This can be non empty only for the streaming time */ List<BluetoothDevice> mLostDevicesWhileStreaming; BluetoothDevice mLostLeadDeviceWhileStreaming; } } List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>(); List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>(); Loading Loading @@ -1066,18 +1066,19 @@ public class LeAudioService extends ProfileService { } } private void clearLostDevicesWhileStreaming(LeAudioGroupDescriptor descriptor) { private void clearLostDevicesWhileStreaming(LeAudioGroupDescriptor descriptor) { for (BluetoothDevice dev : descriptor.mLostDevicesWhileStreaming) { if (DBG) { LeAudioStateMachine sm = mStateMachines.get(dev); Log.d(TAG, " lost dev: " + descriptor.mLostLeadDeviceWhileStreaming); if (sm == null) { continue; } } LeAudioStateMachine sm = mStateMachines.get(descriptor.mLostLeadDeviceWhileStreaming); if (sm != null) { LeAudioStackEvent stackEvent = LeAudioStackEvent stackEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); stackEvent.device = dev; stackEvent.device = descriptor.mLostLeadDeviceWhileStreaming; stackEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED; stackEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED; sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent); sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent); } } descriptor.mLostLeadDeviceWhileStreaming = null; } } // Suppressed since this is part of a local process // Suppressed since this is part of a local process Loading Loading @@ -1110,14 +1111,17 @@ public class LeAudioService extends ProfileService { mActiveAudioOutDevice) mActiveAudioOutDevice) || Objects.equals(device, mActiveAudioInDevice)) || Objects.equals(device, mActiveAudioInDevice)) && (getConnectedPeerDevices(groupId).size() > 1)) { && (getConnectedPeerDevices(groupId).size() > 1)) { descriptor.mLostDevicesWhileStreaming.add(device); if (DBG) Log.d(TAG, "Adding to lost devices : " + device); descriptor.mLostLeadDeviceWhileStreaming = device; return; return; } } break; break; case LeAudioStackEvent.CONNECTION_STATE_CONNECTED: case LeAudioStackEvent.CONNECTION_STATE_CONNECTED: case LeAudioStackEvent.CONNECTION_STATE_CONNECTING: case LeAudioStackEvent.CONNECTION_STATE_CONNECTING: if (descriptor != null) { if (descriptor != null) { descriptor.mLostDevicesWhileStreaming.remove(device); if (DBG) Log.d(TAG, "Removing from lost devices : " + device); descriptor.mLostLeadDeviceWhileStreaming = null; /* Try to connect other devices from the group */ /* Try to connect other devices from the group */ connectSet(device); connectSet(device); } } Loading Loading @@ -1254,6 +1258,7 @@ public class LeAudioService extends ProfileService { ACTIVE_CONTEXTS_NONE, descriptor.mIsActive); ACTIVE_CONTEXTS_NONE, descriptor.mIsActive); notifyGroupStatus = true; notifyGroupStatus = true; /* Clear lost devices */ /* Clear lost devices */ if (DBG) Log.d(TAG, "Clear for group: " + groupId); clearLostDevicesWhileStreaming(descriptor); clearLostDevicesWhileStreaming(descriptor); } } } else { } else { Loading Loading @@ -1538,6 +1543,15 @@ public class LeAudioService extends ProfileService { return; return; } } List<BluetoothDevice> connectedDevices = getConnectedPeerDevices(myGroupId); /* Let's check if the last connected device is really connected */ if (connectedDevices.size() == 1 && Objects.equals(connectedDevices.get(0), descriptor.mLostLeadDeviceWhileStreaming)) { clearLostDevicesWhileStreaming(descriptor); return; } if (getConnectedPeerDevices(myGroupId).isEmpty()){ if (getConnectedPeerDevices(myGroupId).isEmpty()){ descriptor.mIsConnected = false; descriptor.mIsConnected = false; if (descriptor.mIsActive) { if (descriptor.mIsActive) { Loading Loading @@ -2584,9 +2598,8 @@ public class LeAudioService extends ProfileService { ProfileService.println(sb, " mActiveContexts: " + descriptor.mActiveContexts); ProfileService.println(sb, " mActiveContexts: " + descriptor.mActiveContexts); ProfileService.println(sb, " group lead: " + getConnectedGroupLeadDevice(groupId)); ProfileService.println(sb, " group lead: " + getConnectedGroupLeadDevice(groupId)); ProfileService.println(sb, " first device: " + getFirstDeviceFromGroup(groupId)); ProfileService.println(sb, " first device: " + getFirstDeviceFromGroup(groupId)); for (BluetoothDevice dev : descriptor.mLostDevicesWhileStreaming) { ProfileService.println(sb, " lost lead device: " ProfileService.println(sb, " lost device: " + dev); + descriptor.mLostLeadDeviceWhileStreaming); } } } } } } }
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +161 −0 Original line number Original line Diff line number Diff line Loading @@ -26,7 +26,11 @@ import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice; Loading @@ -42,6 +46,7 @@ import android.content.Context; import android.content.Intent; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentFilter; import android.media.AudioManager; import android.media.AudioManager; import android.media.BluetoothProfileConnectionInfo; import android.os.ParcelUuid; import android.os.ParcelUuid; import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry; Loading @@ -68,6 +73,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; import java.util.HashSet; import java.util.HashSet; import java.util.List; import java.util.List; import java.util.Objects; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException; Loading Loading @@ -457,6 +463,23 @@ public class LeAudioServiceTest { .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } } private void injectNoVerifyDeviceConnected(BluetoothDevice device) { generateUnexpectedConnectionMessageFromNative(device, LeAudioStackEvent.CONNECTION_STATE_CONNECTED, LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED); } private void injectAndVerifyDeviceDisconnected(BluetoothDevice device) { generateConnectionMessageFromNative(device, LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED, LeAudioStackEvent.CONNECTION_STATE_CONNECTED); } private void injectNoVerifyDeviceDisconnected(BluetoothDevice device) { generateUnexpectedConnectionMessageFromNative(device, LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED, LeAudioStackEvent.CONNECTION_STATE_CONNECTED); } /** /** * Test that the outgoing connect/disconnect and audio switch is successful. * Test that the outgoing connect/disconnect and audio switch is successful. */ */ Loading Loading @@ -1220,4 +1243,142 @@ public class LeAudioServiceTest { onGroupCodecConfChangedCallbackCalled = false; onGroupCodecConfChangedCallbackCalled = false; mService.mLeAudioCallbacks.unregister(leAudioCallbacks); mService.mLeAudioCallbacks.unregister(leAudioCallbacks); } } private void verifyActiveDeviceStateIntent(int timeoutMs, BluetoothDevice device) { Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device)); assertThat(intent).isNotNull(); assertThat(intent.getAction()) .isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); } /** * Test native interface group status message handling */ @Test public void testLeadGroupDeviceDisconnects() { int groupId = 1; int direction = 1; int snkAudioLocation = 3; int srcAudioLocation = 4; int availableContexts = 5; int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; BluetoothDevice leadDevice; BluetoothDevice memberDevice = mLeftDevice; doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); leadDevice = mService.getConnectedGroupLeadDevice(groupId); if (Objects.equals(leadDevice, mLeftDevice)) { memberDevice = mRightDevice; } assertThat(mService.setActiveDevice(leadDevice)).isTrue(); //Add location support LeAudioStackEvent audioConfChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); audioConfChangedEvent.valueInt1 = direction; audioConfChangedEvent.valueInt2 = groupId; audioConfChangedEvent.valueInt3 = snkAudioLocation; audioConfChangedEvent.valueInt4 = srcAudioLocation; audioConfChangedEvent.valueInt5 = availableContexts; mService.messageFromNative(audioConfChangedEvent); //Set group and device as active LeAudioStackEvent groupStatusChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); groupStatusChangedEvent.valueInt1 = groupId; groupStatusChangedEvent.valueInt2 = groupStatus; mService.messageFromNative(groupStatusChangedEvent); assertThat(mService.getActiveDevices().contains(leadDevice)).isTrue(); verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(leadDevice), any(), any(BluetoothProfileConnectionInfo.class)); verifyActiveDeviceStateIntent(TIMEOUT_MS, leadDevice); injectNoVerifyDeviceDisconnected(leadDevice); // We should not change the audio device assertThat(mService.getConnectionState(leadDevice)) .isEqualTo(BluetoothProfile.STATE_CONNECTED); injectAndVerifyDeviceDisconnected(memberDevice); // Verify the connection state broadcast, and that we are in Connecting state verifyConnectionStateIntent(TIMEOUT_MS, leadDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(any(), eq(leadDevice), any(BluetoothProfileConnectionInfo.class)); } /** * Test native interface group status message handling */ @Test public void testLeadGroupDeviceReconnects() { int groupId = 1; int direction = 1; int snkAudioLocation = 3; int srcAudioLocation = 4; int availableContexts = 5; int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; BluetoothDevice leadDevice; BluetoothDevice memberDevice = mLeftDevice; doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mLeftDevice, groupId); connectTestDevice(mRightDevice, groupId); leadDevice = mService.getConnectedGroupLeadDevice(groupId); if (Objects.equals(leadDevice, mLeftDevice)) { memberDevice = mRightDevice; } assertThat(mService.setActiveDevice(leadDevice)).isTrue(); //Add location support LeAudioStackEvent audioConfChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); audioConfChangedEvent.valueInt1 = direction; audioConfChangedEvent.valueInt2 = groupId; audioConfChangedEvent.valueInt3 = snkAudioLocation; audioConfChangedEvent.valueInt4 = srcAudioLocation; audioConfChangedEvent.valueInt5 = availableContexts; mService.messageFromNative(audioConfChangedEvent); //Set group and device as active LeAudioStackEvent groupStatusChangedEvent = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); groupStatusChangedEvent.valueInt1 = groupId; groupStatusChangedEvent.valueInt2 = groupStatus; mService.messageFromNative(groupStatusChangedEvent); assertThat(mService.getActiveDevices().contains(leadDevice)).isTrue(); verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(leadDevice), any(), any(BluetoothProfileConnectionInfo.class)); verifyActiveDeviceStateIntent(TIMEOUT_MS, leadDevice); /* We don't want to distribute DISCONNECTION event, instead will try to reconnect * (in native) */ injectNoVerifyDeviceDisconnected(leadDevice); assertThat(mService.getConnectionState(leadDevice)) .isEqualTo(BluetoothProfile.STATE_CONNECTED); /* Reconnect device, there should be no intent about that, as device was pretending * connected */ injectNoVerifyDeviceConnected(leadDevice); injectAndVerifyDeviceDisconnected(memberDevice); injectAndVerifyDeviceDisconnected(leadDevice); verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), eq(leadDevice), any(BluetoothProfileConnectionInfo.class)); } } }
system/bta/csis/csis_client.cc +9 −3 Original line number Original line Diff line number Diff line Loading @@ -1558,9 +1558,15 @@ class CsisClientImpl : public CsisClient { OnGattNotification(p_data->notify); OnGattNotification(p_data->notify); break; break; case BTA_GATTC_ENC_CMPL_CB_EVT: case BTA_GATTC_ENC_CMPL_CB_EVT: { OnLeEncryptionComplete(p_data->enc_cmpl.remote_bda, BTM_SUCCESS); uint8_t encryption_status; break; if (BTM_IsEncrypted(p_data->enc_cmpl.remote_bda, BT_TRANSPORT_LE)) { encryption_status = BTM_SUCCESS; } else { encryption_status = BTM_FAILED_ON_SECURITY; } OnLeEncryptionComplete(p_data->enc_cmpl.remote_bda, encryption_status); } break; case BTA_GATTC_SRVC_CHG_EVT: case BTA_GATTC_SRVC_CHG_EVT: OnGattServiceChangeEvent(p_data->remote_bda); OnGattServiceChangeEvent(p_data->remote_bda); Loading