summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--framework/tests/bumble/src/android/bluetooth/Host.kt24
-rw-r--r--framework/tests/bumble/src/android/bluetooth/RfcommTest.kt148
2 files changed, 162 insertions, 10 deletions
diff --git a/framework/tests/bumble/src/android/bluetooth/Host.kt b/framework/tests/bumble/src/android/bluetooth/Host.kt
index af0736df61..2be0c75d9e 100644
--- a/framework/tests/bumble/src/android/bluetooth/Host.kt
+++ b/framework/tests/bumble/src/android/bluetooth/Host.kt
@@ -16,6 +16,7 @@
package android.bluetooth
+import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -40,6 +41,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
+@SuppressLint("MissingPermission")
@kotlinx.coroutines.ExperimentalCoroutinesApi
public class Host(context: Context) : Closeable {
private val TAG = "PandoraHost"
@@ -54,6 +56,7 @@ public class Host(context: Context) : Closeable {
val intentFilter = IntentFilter()
intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
+ intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
flow = intentFlow(context, intentFilter, scope).shareIn(scope, SharingStarted.Eagerly)
}
@@ -113,11 +116,32 @@ public class Host(context: Context) : Closeable {
it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) ==
BluetoothDevice.BOND_NONE
}
+ .first()
Log.d(TAG, "removeBondAndVerify: done")
}
}
}
+ fun disconnectAndVerify(remoteDevice: BluetoothDevice) {
+ Log.d(TAG, "disconnectAndVerify: $remoteDevice")
+ runBlocking(scope.coroutineContext) {
+ withTimeout(TIMEOUT) {
+ assertThat(remoteDevice.disconnect()).isEqualTo(BluetoothStatusCodes.SUCCESS)
+ flow
+ .filter { it.getAction() == BluetoothDevice.ACTION_ACL_DISCONNECTED }
+ .filter {
+ it.getIntExtra(
+ BluetoothDevice.EXTRA_TRANSPORT,
+ BluetoothDevice.TRANSPORT_AUTO,
+ ) == BluetoothDevice.TRANSPORT_BREDR
+ }
+ .filter { it.getBluetoothDeviceExtra() == remoteDevice }
+ .first()
+ Log.d(TAG, "disconnectAndVerify: done")
+ }
+ }
+ }
+
fun Intent.getBluetoothDeviceExtra(): BluetoothDevice =
this.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)!!
diff --git a/framework/tests/bumble/src/android/bluetooth/RfcommTest.kt b/framework/tests/bumble/src/android/bluetooth/RfcommTest.kt
index 0a97bdfd9a..40dec912f7 100644
--- a/framework/tests/bumble/src/android/bluetooth/RfcommTest.kt
+++ b/framework/tests/bumble/src/android/bluetooth/RfcommTest.kt
@@ -16,6 +16,7 @@
package android.bluetooth
import android.Manifest
+import android.annotation.SuppressLint
import android.bluetooth.test_utils.EnableBluetoothRule
import android.content.Context
import androidx.test.core.app.ApplicationProvider
@@ -34,10 +35,16 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.timeout
+import org.mockito.kotlin.verify
import pandora.RfcommProto
import pandora.RfcommProto.ServerId
import pandora.RfcommProto.StartServerRequest
+@SuppressLint("MissingPermission")
@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class RfcommTest {
@@ -52,7 +59,8 @@ class RfcommTest {
AdoptShellPermissionsRule(
InstrumentationRegistry.getInstrumentation().getUiAutomation(),
Manifest.permission.BLUETOOTH_CONNECT,
- Manifest.permission.BLUETOOTH_PRIVILEGED
+ Manifest.permission.BLUETOOTH_PRIVILEGED,
+ Manifest.permission.MODIFY_PHONE_STATE,
)
// Set up a Bumble Pandora device for the duration of the test.
@@ -60,21 +68,41 @@ class RfcommTest {
@Rule(order = 2) @JvmField val enableBluetoothRule = EnableBluetoothRule(false, true)
- private lateinit var mBumbleDevice: BluetoothDevice
+ private lateinit var mRemoteDevice: BluetoothDevice
private lateinit var host: Host
private var mConnectionCounter = 1
+ private var mProfileServiceListener = mock<BluetoothProfile.ServiceListener>()
@Before
fun setUp() {
- mBumbleDevice = mBumble.remoteDevice
+ mRemoteDevice = mBumble.remoteDevice
host = Host(mContext)
- host.createBondAndVerify(mBumbleDevice)
+ val bluetoothA2dp = getProfileProxy(mContext, BluetoothProfile.A2DP) as BluetoothA2dp
+ bluetoothA2dp.setConnectionPolicy(
+ mRemoteDevice,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+ )
+ val bluetoothHfp = getProfileProxy(mContext, BluetoothProfile.HEADSET) as BluetoothHeadset
+ bluetoothHfp.setConnectionPolicy(
+ mRemoteDevice,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+ )
+ val bluetoothHidHost =
+ getProfileProxy(mContext, BluetoothProfile.HID_HOST) as BluetoothHidHost
+ bluetoothHidHost.setConnectionPolicy(
+ mRemoteDevice,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+ )
+ host.createBondAndVerify(mRemoteDevice)
+ if (mRemoteDevice.isConnected) {
+ host.disconnectAndVerify(mRemoteDevice)
+ }
}
@After
fun tearDown() {
- if (mAdapter.bondedDevices.contains(mBumbleDevice)) {
- host.removeBondAndVerify(mBumbleDevice)
+ if (mAdapter.bondedDevices.contains(mRemoteDevice)) {
+ host.removeBondAndVerify(mRemoteDevice)
}
host.close()
}
@@ -159,12 +187,104 @@ class RfcommTest {
}
}
+ @Test
+ fun connectTwoInsecureClientsSimultaneously() {
+ startServer("ServerPort1", TEST_UUID) { serverId1 ->
+ startServer("ServerPort2", SERIAL_PORT_UUID) { serverId2 ->
+ val socket1 = createSocket(mRemoteDevice, isSecure = false, TEST_UUID)
+ val socket2 = createSocket(mRemoteDevice, isSecure = false, SERIAL_PORT_UUID)
+
+ acceptSocket(serverId1)
+ Truth.assertThat(socket1.isConnected).isTrue()
+
+ acceptSocket(serverId2)
+ Truth.assertThat(socket2.isConnected).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun connectTwoInsecureClientsSequentially() {
+ startServer("ServerPort1", TEST_UUID) { serverId1 ->
+ startServer("ServerPort2", SERIAL_PORT_UUID) { serverId2 ->
+ val socket1 = createSocket(mRemoteDevice, isSecure = false, TEST_UUID)
+ acceptSocket(serverId1)
+ Truth.assertThat(socket1.isConnected).isTrue()
+
+ val socket2 = createSocket(mRemoteDevice, isSecure = false, SERIAL_PORT_UUID)
+ acceptSocket(serverId2)
+ Truth.assertThat(socket2.isConnected).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun connectTwoSecureClientsSimultaneously() {
+ startServer("ServerPort1", TEST_UUID) { serverId1 ->
+ startServer("ServerPort2", SERIAL_PORT_UUID) { serverId2 ->
+ val socket2 = createSocket(mRemoteDevice, isSecure = true, SERIAL_PORT_UUID)
+ val socket1 = createSocket(mRemoteDevice, isSecure = true, TEST_UUID)
+
+ acceptSocket(serverId1)
+ Truth.assertThat(socket1.isConnected).isTrue()
+
+ acceptSocket(serverId2)
+ Truth.assertThat(socket2.isConnected).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun connectTwoSecureClientsSequentially() {
+ startServer("ServerPort1", TEST_UUID) { serverId1 ->
+ startServer("ServerPort2", SERIAL_PORT_UUID) { serverId2 ->
+ val socket1 = createSocket(mRemoteDevice, isSecure = true, TEST_UUID)
+ acceptSocket(serverId1)
+ Truth.assertThat(socket1.isConnected).isTrue()
+
+ val socket2 = createSocket(mRemoteDevice, isSecure = true, SERIAL_PORT_UUID)
+ acceptSocket(serverId2)
+ Truth.assertThat(socket2.isConnected).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun connectTwoMixedClientsInsecureThenSecure() {
+ startServer("ServerPort1", TEST_UUID) { serverId1 ->
+ startServer("ServerPort2", SERIAL_PORT_UUID) { serverId2 ->
+ val socket2 = createSocket(mRemoteDevice, isSecure = false, SERIAL_PORT_UUID)
+ acceptSocket(serverId2)
+ Truth.assertThat(socket2.isConnected).isTrue()
+
+ val socket1 = createSocket(mRemoteDevice, isSecure = true, TEST_UUID)
+ acceptSocket(serverId1)
+ Truth.assertThat(socket1.isConnected).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun connectTwoMixedClientsSecureThenInsecure() {
+ startServer("ServerPort1", TEST_UUID) { serverId1 ->
+ startServer("ServerPort2", SERIAL_PORT_UUID) { serverId2 ->
+ val socket2 = createSocket(mRemoteDevice, isSecure = true, SERIAL_PORT_UUID)
+ acceptSocket(serverId2)
+ Truth.assertThat(socket2.isConnected).isTrue()
+
+ val socket1 = createSocket(mRemoteDevice, isSecure = false, TEST_UUID)
+ acceptSocket(serverId1)
+ Truth.assertThat(socket1.isConnected).isTrue()
+ }
+ }
+ }
+
private fun createConnectAcceptSocket(
isSecure: Boolean,
server: ServerId,
- uuid: String = TEST_UUID
+ uuid: String = TEST_UUID,
): Pair<BluetoothSocket, RfcommProto.RfcommConnection> {
- val socket = createSocket(mBumbleDevice, isSecure, uuid)
+ val socket = createSocket(mRemoteDevice, isSecure, uuid)
val connection = acceptSocket(server)
Truth.assertThat(socket.isConnected).isTrue()
@@ -175,7 +295,7 @@ class RfcommTest {
private fun createSocket(
device: BluetoothDevice,
isSecure: Boolean,
- uuid: String
+ uuid: String,
): BluetoothSocket {
val socket =
if (isSecure) {
@@ -204,7 +324,7 @@ class RfcommTest {
private fun startServer(
name: String = TEST_SERVER_NAME,
uuid: String = TEST_UUID,
- block: (ServerId) -> Unit
+ block: (ServerId) -> Unit,
) {
val request = StartServerRequest.newBuilder().setName(name).setUuid(uuid).build()
val response = mBumble.rfcommBlocking().startServer(request)
@@ -221,6 +341,14 @@ class RfcommTest {
}
}
+ private fun getProfileProxy(context: Context, profile: Int): BluetoothProfile {
+ mAdapter.getProfileProxy(context, mProfileServiceListener, profile)
+ val proxyCaptor = argumentCaptor<BluetoothProfile>()
+ verify(mProfileServiceListener, timeout(GRPC_TIMEOUT.toMillis()))
+ .onServiceConnected(eq(profile), proxyCaptor.capture())
+ return proxyCaptor.lastValue
+ }
+
companion object {
private val TAG = RfcommTest::class.java.getSimpleName()
private val GRPC_TIMEOUT = Duration.ofSeconds(10)