Loading android/app/src/com/android/bluetooth/a2dp/A2dpService.java +108 −26 Original line number Diff line number Diff line Loading @@ -207,6 +207,7 @@ public class A2dpService extends ProfileService { } if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { Log.e(TAG, "Cannot connect to " + device + " : PRIORITY_OFF"); return false; } if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device), Loading @@ -216,6 +217,10 @@ public class A2dpService extends ProfileService { } synchronized (mStateMachines) { if (!connectionAllowedCheckMaxDevices(device)) { Log.e(TAG, "Cannot connect to " + device + " : too many connected devices"); return false; } A2dpStateMachine smConnect = getOrCreateStateMachine(device); if (smConnect == null) { Log.e(TAG, "Cannot connect to " + device + " : no state machine"); Loading Loading @@ -263,8 +268,7 @@ public class A2dpService extends ProfileService { * @param device the peer device to connect to * @return true if connection is allowed, otherwise false */ @VisibleForTesting public boolean canConnectToDevice(BluetoothDevice device) { private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) { int connected = 0; // Count devices that are in the process of connecting or already connected synchronized (mStateMachines) { Loading Loading @@ -300,7 +304,7 @@ public class A2dpService extends ProfileService { return false; } // Check if too many devices if (!canConnectToDevice(device)) { if (!connectionAllowedCheckMaxDevices(device)) { Log.e(TAG, "okToConnect: cannot connect to " + device + " : too many connected devices"); return false; Loading Loading @@ -348,6 +352,22 @@ public class A2dpService extends ProfileService { } } /** * Get the list of devices that have state machines. * * @return the list of devices that have state machines */ @VisibleForTesting List<BluetoothDevice> getDevices() { List<BluetoothDevice> devices = new ArrayList<>(); synchronized (mStateMachines) { for (A2dpStateMachine sm : mStateMachines.values()) { devices.add(sm.getDevice()); } return devices; } } public int getConnectionState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); synchronized (mStateMachines) { Loading Loading @@ -639,12 +659,30 @@ public class A2dpService extends ProfileService { // Handle messages from native (JNI) to Java void messageFromNative(A2dpStackEvent stackEvent) { Objects.requireNonNull(stackEvent.device, "Device should never be null, event: " + stackEvent); synchronized (mStateMachines) { A2dpStateMachine sm = null; BluetoothDevice device = stackEvent.device; A2dpStateMachine sm = getOrCreateStateMachine(device); if (stackEvent.type == A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { switch (stackEvent.valueInt) { case A2dpStackEvent.CONNECTION_STATE_CONNECTED: case A2dpStackEvent.CONNECTION_STATE_CONNECTING: // Create a new state machine only when connecting to a device if (!connectionAllowedCheckMaxDevices(device)) { Log.e(TAG, "Cannot connect to " + device + " : too many connected devices"); return; } sm = getOrCreateStateMachine(device); break; default: sm = mStateMachines.get(device); break; } } if (sm == null) { Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); return; } sm.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent); Loading Loading @@ -682,7 +720,7 @@ public class A2dpService extends ProfileService { return sm; } // Limit the maximum number of state machines to avoid DoS attack if (mStateMachines.size() > MAX_A2DP_STATE_MACHINES) { if (mStateMachines.size() >= MAX_A2DP_STATE_MACHINES) { Log.e(TAG, "Maximum number of A2DP state machines reached: " + MAX_A2DP_STATE_MACHINES); return null; Loading Loading @@ -721,7 +759,6 @@ public class A2dpService extends ProfileService { sendBroadcast(intent, A2dpService.BLUETOOTH_PERM); } // Remove state machine if the bonding for a device is removed private class BondStateChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading @@ -731,10 +768,27 @@ public class A2dpService extends ProfileService { int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); bondStateChanged(device, state); } } /** * Process a change in the bonding state for a device. * * @param device the device whose bonding state has changed * @param bondState the new bond state for the device. Possible values are: * {@link BluetoothDevice#BOND_NONE}, * {@link BluetoothDevice#BOND_BONDING}, * {@link BluetoothDevice#BOND_BONDED}. */ @VisibleForTesting void bondStateChanged(BluetoothDevice device, int bondState) { if (DBG) { Log.d(TAG, "Bond state changed for device: " + device + " state: " + state); Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); } if (state != BluetoothDevice.BOND_NONE) { // Remove state machine if the bonding for a device is removed if (bondState != BluetoothDevice.BOND_NONE) { return; } synchronized (mStateMachines) { Loading @@ -742,22 +796,34 @@ public class A2dpService extends ProfileService { if (sm == null) { return; } if (DBG) { Log.d(TAG, "Removing state machine for device: " + device); if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { return; } removeStateMachine(device); } } private void removeStateMachine(BluetoothDevice device) { synchronized (mStateMachines) { A2dpStateMachine sm = mStateMachines.get(device); if (sm == null) { Log.w(TAG, "removeStateMachine: device " + device + " does not have a state machine"); return; } Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); sm.doQuit(); sm.cleanup(); mStateMachines.remove(device); } } } private void updateOptionalCodecsSupport(BluetoothDevice device) { int previousSupport = getSupportsOptionalCodecs(device); boolean supportsOptional = false; synchronized (mStateMachines) { A2dpStateMachine sm = getOrCreateStateMachine(device); A2dpStateMachine sm = mStateMachines.get(device); if (sm == null) { return; } Loading Loading @@ -801,14 +867,28 @@ public class A2dpService extends ProfileService { if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) { setActiveDevice(device); } // Check if the active device has been disconnected // Check if the active device is not connected anymore if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) { setActiveDevice(null); } // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { int bondState = mAdapterService.getBondState(device); if (bondState == BluetoothDevice.BOND_NONE) { removeStateMachine(device); } } } } // Update codec support per device when device is (re)connected. /** * Receiver for processing device connection state changes. * * <ul> * <li> Update codec support per device when device is (re)connected * <li> Delete the state machine instance if the device is disconnected and unbond * </ul> */ private class ConnectionStateChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading @@ -822,7 +902,9 @@ public class A2dpService extends ProfileService { } } // Binder object: Must be static class or memory leak may occur /** * Binder object: must be a static class or memory leak may occur. */ @VisibleForTesting static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub implements IProfileServiceBinder { Loading android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java +22 −12 Original line number Diff line number Diff line Loading @@ -223,8 +223,8 @@ final class A2dpStateMachine extends StateMachine { } // in Disconnected state private void processConnectionEvent(int state) { switch (state) { private void processConnectionEvent(int event) { switch (event) { case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice); break; Loading Loading @@ -253,7 +253,7 @@ final class A2dpStateMachine extends StateMachine { Log.w(TAG, "Ignore A2DP DISCONNECTING event: " + mDevice); break; default: Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice); Log.e(TAG, "Incorrect event: " + event + " device: " + mDevice); break; } } Loading Loading @@ -333,8 +333,8 @@ final class A2dpStateMachine extends StateMachine { } // in Connecting state private void processConnectionEvent(int state) { switch (state) { private void processConnectionEvent(int event) { switch (event) { case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: Log.w(TAG, "Connecting device disconnected: " + mDevice); transitionTo(mDisconnected); Loading @@ -350,7 +350,7 @@ final class A2dpStateMachine extends StateMachine { transitionTo(mDisconnecting); break; default: Log.e(TAG, "Incorrect state: " + state); Log.e(TAG, "Incorrect event: " + event); break; } } Loading Loading @@ -427,8 +427,8 @@ final class A2dpStateMachine extends StateMachine { } // in Disconnecting state private void processConnectionEvent(int state) { switch (state) { private void processConnectionEvent(int event) { switch (event) { case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: Log.i(TAG, "Disconnected: " + mDevice); transitionTo(mDisconnected); Loading Loading @@ -457,7 +457,7 @@ final class A2dpStateMachine extends StateMachine { // We are already disconnecting, do nothing break; default: Log.e(TAG, "Incorrect state: " + state); Log.e(TAG, "Incorrect event: " + event); break; } } Loading Loading @@ -546,14 +546,24 @@ final class A2dpStateMachine extends StateMachine { } // in Connected state private void processConnectionEvent(int state) { switch (state) { private void processConnectionEvent(int event) { switch (event) { case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: Log.i(TAG, "Disconnected from " + mDevice); transitionTo(mDisconnected); break; case A2dpStackEvent.CONNECTION_STATE_CONNECTED: Log.w(TAG, "Ignore A2DP CONNECTED event: " + mDevice); break; case A2dpStackEvent.CONNECTION_STATE_CONNECTING: Log.w(TAG, "Ignore A2DP CONNECTING event: " + mDevice); break; case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: Log.i(TAG, "Disconnecting from " + mDevice); transitionTo(mDisconnecting); break; default: Log.e(TAG, "Connection State Device: " + mDevice + " bad state: " + state); Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event); break; } } Loading android/app/tests/unit/src/com/android/bluetooth/TestUtils.java +17 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.support.test.InstrumentationRegistry; import android.support.test.rule.ServiceTestRule; Loading Loading @@ -148,4 +149,20 @@ public class TestUtils { verify(adapterService, timeout(SERVICE_TOGGLE_TIMEOUT_MS)).onProfileServiceStateChanged( eq(profileServiceClass.getName()), eq(BluetoothAdapter.STATE_OFF)); } /** * Create a test device. * * @param bluetoothAdapter the Bluetooth adapter to use * @param id the test device ID. It must be an integer in the interval [0, 0xFF]. * @return {@link BluetoothDevice} test device for the device ID */ public static BluetoothDevice getTestDevice(BluetoothAdapter bluetoothAdapter, int id) { Assert.assertTrue(id <= 0xFF); Assert.assertNotNull(bluetoothAdapter); BluetoothDevice testDevice = bluetoothAdapter.getRemoteDevice(String.format("00:01:02:03:04:%02X", id)); Assert.assertNotNull(testDevice); return testDevice; } } android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java +285 −3 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
android/app/src/com/android/bluetooth/a2dp/A2dpService.java +108 −26 Original line number Diff line number Diff line Loading @@ -207,6 +207,7 @@ public class A2dpService extends ProfileService { } if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { Log.e(TAG, "Cannot connect to " + device + " : PRIORITY_OFF"); return false; } if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device), Loading @@ -216,6 +217,10 @@ public class A2dpService extends ProfileService { } synchronized (mStateMachines) { if (!connectionAllowedCheckMaxDevices(device)) { Log.e(TAG, "Cannot connect to " + device + " : too many connected devices"); return false; } A2dpStateMachine smConnect = getOrCreateStateMachine(device); if (smConnect == null) { Log.e(TAG, "Cannot connect to " + device + " : no state machine"); Loading Loading @@ -263,8 +268,7 @@ public class A2dpService extends ProfileService { * @param device the peer device to connect to * @return true if connection is allowed, otherwise false */ @VisibleForTesting public boolean canConnectToDevice(BluetoothDevice device) { private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) { int connected = 0; // Count devices that are in the process of connecting or already connected synchronized (mStateMachines) { Loading Loading @@ -300,7 +304,7 @@ public class A2dpService extends ProfileService { return false; } // Check if too many devices if (!canConnectToDevice(device)) { if (!connectionAllowedCheckMaxDevices(device)) { Log.e(TAG, "okToConnect: cannot connect to " + device + " : too many connected devices"); return false; Loading Loading @@ -348,6 +352,22 @@ public class A2dpService extends ProfileService { } } /** * Get the list of devices that have state machines. * * @return the list of devices that have state machines */ @VisibleForTesting List<BluetoothDevice> getDevices() { List<BluetoothDevice> devices = new ArrayList<>(); synchronized (mStateMachines) { for (A2dpStateMachine sm : mStateMachines.values()) { devices.add(sm.getDevice()); } return devices; } } public int getConnectionState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); synchronized (mStateMachines) { Loading Loading @@ -639,12 +659,30 @@ public class A2dpService extends ProfileService { // Handle messages from native (JNI) to Java void messageFromNative(A2dpStackEvent stackEvent) { Objects.requireNonNull(stackEvent.device, "Device should never be null, event: " + stackEvent); synchronized (mStateMachines) { A2dpStateMachine sm = null; BluetoothDevice device = stackEvent.device; A2dpStateMachine sm = getOrCreateStateMachine(device); if (stackEvent.type == A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { switch (stackEvent.valueInt) { case A2dpStackEvent.CONNECTION_STATE_CONNECTED: case A2dpStackEvent.CONNECTION_STATE_CONNECTING: // Create a new state machine only when connecting to a device if (!connectionAllowedCheckMaxDevices(device)) { Log.e(TAG, "Cannot connect to " + device + " : too many connected devices"); return; } sm = getOrCreateStateMachine(device); break; default: sm = mStateMachines.get(device); break; } } if (sm == null) { Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); return; } sm.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent); Loading Loading @@ -682,7 +720,7 @@ public class A2dpService extends ProfileService { return sm; } // Limit the maximum number of state machines to avoid DoS attack if (mStateMachines.size() > MAX_A2DP_STATE_MACHINES) { if (mStateMachines.size() >= MAX_A2DP_STATE_MACHINES) { Log.e(TAG, "Maximum number of A2DP state machines reached: " + MAX_A2DP_STATE_MACHINES); return null; Loading Loading @@ -721,7 +759,6 @@ public class A2dpService extends ProfileService { sendBroadcast(intent, A2dpService.BLUETOOTH_PERM); } // Remove state machine if the bonding for a device is removed private class BondStateChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading @@ -731,10 +768,27 @@ public class A2dpService extends ProfileService { int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); bondStateChanged(device, state); } } /** * Process a change in the bonding state for a device. * * @param device the device whose bonding state has changed * @param bondState the new bond state for the device. Possible values are: * {@link BluetoothDevice#BOND_NONE}, * {@link BluetoothDevice#BOND_BONDING}, * {@link BluetoothDevice#BOND_BONDED}. */ @VisibleForTesting void bondStateChanged(BluetoothDevice device, int bondState) { if (DBG) { Log.d(TAG, "Bond state changed for device: " + device + " state: " + state); Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); } if (state != BluetoothDevice.BOND_NONE) { // Remove state machine if the bonding for a device is removed if (bondState != BluetoothDevice.BOND_NONE) { return; } synchronized (mStateMachines) { Loading @@ -742,22 +796,34 @@ public class A2dpService extends ProfileService { if (sm == null) { return; } if (DBG) { Log.d(TAG, "Removing state machine for device: " + device); if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { return; } removeStateMachine(device); } } private void removeStateMachine(BluetoothDevice device) { synchronized (mStateMachines) { A2dpStateMachine sm = mStateMachines.get(device); if (sm == null) { Log.w(TAG, "removeStateMachine: device " + device + " does not have a state machine"); return; } Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); sm.doQuit(); sm.cleanup(); mStateMachines.remove(device); } } } private void updateOptionalCodecsSupport(BluetoothDevice device) { int previousSupport = getSupportsOptionalCodecs(device); boolean supportsOptional = false; synchronized (mStateMachines) { A2dpStateMachine sm = getOrCreateStateMachine(device); A2dpStateMachine sm = mStateMachines.get(device); if (sm == null) { return; } Loading Loading @@ -801,14 +867,28 @@ public class A2dpService extends ProfileService { if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) { setActiveDevice(device); } // Check if the active device has been disconnected // Check if the active device is not connected anymore if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) { setActiveDevice(null); } // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { int bondState = mAdapterService.getBondState(device); if (bondState == BluetoothDevice.BOND_NONE) { removeStateMachine(device); } } } } // Update codec support per device when device is (re)connected. /** * Receiver for processing device connection state changes. * * <ul> * <li> Update codec support per device when device is (re)connected * <li> Delete the state machine instance if the device is disconnected and unbond * </ul> */ private class ConnectionStateChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading @@ -822,7 +902,9 @@ public class A2dpService extends ProfileService { } } // Binder object: Must be static class or memory leak may occur /** * Binder object: must be a static class or memory leak may occur. */ @VisibleForTesting static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub implements IProfileServiceBinder { Loading
android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java +22 −12 Original line number Diff line number Diff line Loading @@ -223,8 +223,8 @@ final class A2dpStateMachine extends StateMachine { } // in Disconnected state private void processConnectionEvent(int state) { switch (state) { private void processConnectionEvent(int event) { switch (event) { case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice); break; Loading Loading @@ -253,7 +253,7 @@ final class A2dpStateMachine extends StateMachine { Log.w(TAG, "Ignore A2DP DISCONNECTING event: " + mDevice); break; default: Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice); Log.e(TAG, "Incorrect event: " + event + " device: " + mDevice); break; } } Loading Loading @@ -333,8 +333,8 @@ final class A2dpStateMachine extends StateMachine { } // in Connecting state private void processConnectionEvent(int state) { switch (state) { private void processConnectionEvent(int event) { switch (event) { case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: Log.w(TAG, "Connecting device disconnected: " + mDevice); transitionTo(mDisconnected); Loading @@ -350,7 +350,7 @@ final class A2dpStateMachine extends StateMachine { transitionTo(mDisconnecting); break; default: Log.e(TAG, "Incorrect state: " + state); Log.e(TAG, "Incorrect event: " + event); break; } } Loading Loading @@ -427,8 +427,8 @@ final class A2dpStateMachine extends StateMachine { } // in Disconnecting state private void processConnectionEvent(int state) { switch (state) { private void processConnectionEvent(int event) { switch (event) { case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: Log.i(TAG, "Disconnected: " + mDevice); transitionTo(mDisconnected); Loading Loading @@ -457,7 +457,7 @@ final class A2dpStateMachine extends StateMachine { // We are already disconnecting, do nothing break; default: Log.e(TAG, "Incorrect state: " + state); Log.e(TAG, "Incorrect event: " + event); break; } } Loading Loading @@ -546,14 +546,24 @@ final class A2dpStateMachine extends StateMachine { } // in Connected state private void processConnectionEvent(int state) { switch (state) { private void processConnectionEvent(int event) { switch (event) { case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: Log.i(TAG, "Disconnected from " + mDevice); transitionTo(mDisconnected); break; case A2dpStackEvent.CONNECTION_STATE_CONNECTED: Log.w(TAG, "Ignore A2DP CONNECTED event: " + mDevice); break; case A2dpStackEvent.CONNECTION_STATE_CONNECTING: Log.w(TAG, "Ignore A2DP CONNECTING event: " + mDevice); break; case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: Log.i(TAG, "Disconnecting from " + mDevice); transitionTo(mDisconnecting); break; default: Log.e(TAG, "Connection State Device: " + mDevice + " bad state: " + state); Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event); break; } } Loading
android/app/tests/unit/src/com/android/bluetooth/TestUtils.java +17 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.support.test.InstrumentationRegistry; import android.support.test.rule.ServiceTestRule; Loading Loading @@ -148,4 +149,20 @@ public class TestUtils { verify(adapterService, timeout(SERVICE_TOGGLE_TIMEOUT_MS)).onProfileServiceStateChanged( eq(profileServiceClass.getName()), eq(BluetoothAdapter.STATE_OFF)); } /** * Create a test device. * * @param bluetoothAdapter the Bluetooth adapter to use * @param id the test device ID. It must be an integer in the interval [0, 0xFF]. * @return {@link BluetoothDevice} test device for the device ID */ public static BluetoothDevice getTestDevice(BluetoothAdapter bluetoothAdapter, int id) { Assert.assertTrue(id <= 0xFF); Assert.assertNotNull(bluetoothAdapter); BluetoothDevice testDevice = bluetoothAdapter.getRemoteDevice(String.format("00:01:02:03:04:%02X", id)); Assert.assertNotNull(testDevice); return testDevice; } }
android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java +285 −3 File changed.Preview size limit exceeded, changes collapsed. Show changes