Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6713dc6 authored by Rahul Arya's avatar Rahul Arya Committed by Automerger Merge Worker
Browse files

[Pandora] Add control over BREDR connections am: 82a5949a

parents 8299028c 82a5949a
Loading
Loading
Loading
Loading
+20 −2
Original line number Diff line number Diff line
@@ -58,15 +58,33 @@ message ReadLocalAddressResponse {
// A Token representing an ACL connection.
// It's acquired via a Connect on the Host service.
message Connection {
  // Opaque value filled by the gRPC server, must not
  // be modified nor crafted.
  // Opaque value filled by the gRPC server, must not be modified nor crafted
  // Android specific: it's secretly an encoded InternelConnectionRef created using newConnection
  bytes cookie = 1;
}

// Internal representation of a Connection - not exposed to clients, included here
// just for code-generation convenience
message InternalConnectionRef {
  bytes address = 1;
  Transport transport = 2;
}

// WARNING: Leaving this enum empty will default to BREDR, so make sure that this is a
// valid default whenever used, and that we always populate this value.
enum Transport {
  TRANSPORT_BREDR = 0;
  TRANSPORT_LE = 1;
}

// Request of the `Connect` method.
message ConnectRequest {
  // Peer Bluetooth Device Address as array of 6 bytes.
  bytes address = 1;
  // Whether we want to initiate pairing as part of the connection
  bool skip_pairing = 2;
  // Whether confirmation prompts should be auto-accepted or handled manually
  bool manually_confirm = 3;
}

// Response of the `Connect` method.
+8 −8
Original line number Diff line number Diff line
@@ -74,7 +74,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
    grpcUnary<ExchangeMTUResponse>(mScope, responseObserver) {
      val mtu = request.mtu
      Log.i(TAG, "exchangeMTU MTU=$mtu")
      if (!GattInstance.get(request.connection.cookie).mGatt.requestMtu(mtu)) {
      if (!GattInstance.get(request.connection.address).mGatt.requestMtu(mtu)) {
        Log.e(TAG, "Error on requesting MTU $mtu")
        throw Status.UNKNOWN.asException()
      }
@@ -88,7 +88,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
  ) {
    grpcUnary<WriteResponse>(mScope, responseObserver) {
      Log.i(TAG, "writeAttFromHandle handle=${request.handle}")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val gattInstance = GattInstance.get(request.connection.address)
      var characteristic: BluetoothGattCharacteristic? =
          getCharacteristicWithHandle(request.handle, gattInstance)
      if (characteristic == null) {
@@ -113,7 +113,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
      responseObserver: StreamObserver<DiscoverServicesResponse>
  ) {
    grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) {
      val gattInstance = GattInstance.get(request.connection.cookie)
      val gattInstance = GattInstance.get(request.connection.address)
      Log.i(TAG, "discoverServiceByUuid uuid=${request.uuid}")
      // In some cases, GATT starts a discovery immediately after being connected, so
      // we need to wait until the service discovery is finished to be able to discover again.
@@ -133,7 +133,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
  ) {
    grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) {
      Log.i(TAG, "discoverServices")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val gattInstance = GattInstance.get(request.connection.address)
      check(gattInstance.mGatt.discoverServices())
      gattInstance.waitForDiscoveryEnd()
      DiscoverServicesResponse.newBuilder()
@@ -168,7 +168,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
  ) {
    grpcUnary<ClearCacheResponse>(mScope, responseObserver) {
      Log.i(TAG, "clearCache")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val gattInstance = GattInstance.get(request.connection.address)
      check(gattInstance.mGatt.refresh())
      ClearCacheResponse.newBuilder().build()
    }
@@ -180,7 +180,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
  ) {
    grpcUnary<ReadCharacteristicResponse>(mScope, responseObserver) {
      Log.i(TAG, "readCharacteristicFromHandle handle=${request.handle}")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val gattInstance = GattInstance.get(request.connection.address)
      val characteristic: BluetoothGattCharacteristic? =
          getCharacteristicWithHandle(request.handle, gattInstance)
      checkNotNull(characteristic) { "Characteristic handle ${request.handle} not found." }
@@ -198,7 +198,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
  ) {
    grpcUnary<ReadCharacteristicsFromUuidResponse>(mScope, responseObserver) {
      Log.i(TAG, "readCharacteristicsFromUuid uuid=${request.uuid}")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val gattInstance = GattInstance.get(request.connection.address)
      tryDiscoverServices(gattInstance)
      val readValues =
          gattInstance.readCharacteristicUuidBlocking(
@@ -215,7 +215,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
  ) {
    grpcUnary<ReadCharacteristicDescriptorResponse>(mScope, responseObserver) {
      Log.i(TAG, "readCharacteristicDescriptorFromHandle handle=${request.handle}")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val gattInstance = GattInstance.get(request.connection.address)
      val descriptor: BluetoothGattDescriptor? =
          getDescriptorWithHandle(request.handle, gattInstance)
      checkNotNull(descriptor) { "Descriptor handle ${request.handle} not found." }
+30 −27
Original line number Diff line number Diff line
@@ -37,6 +37,9 @@ import com.google.protobuf.ByteString
import com.google.protobuf.Empty
import io.grpc.Status
import io.grpc.stub.StreamObserver
import java.io.IOException
import java.time.Duration
import java.util.UUID
import kotlin.Result.Companion.failure
import kotlin.Result.Companion.success
import kotlin.coroutines.suspendCoroutine
@@ -57,6 +60,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import pandora.HostGrpc.HostImplBase
import pandora.HostProto.*

@@ -219,11 +223,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB
      acceptPairingAndAwaitBonded(bluetoothDevice)

      WaitConnectionResponse.newBuilder()
        .setConnection(
          Connection.newBuilder()
            .setCookie(ByteString.copyFromUtf8(bluetoothDevice.address))
            .build()
        )
        .setConnection(newConnection(bluetoothDevice, Transport.TRANSPORT_BREDR))
        .build()
    }
  }
@@ -276,7 +276,17 @@ class Host(private val context: Context, private val server: Server) : HostImplB

      Log.i(TAG, "connect: address=$bluetoothDevice")

      bluetoothAdapter.cancelDiscovery()

      if (!bluetoothDevice.isConnected()) {
        if (request.skipPairing) {
          // do an SDP request to trigger a temporary BREDR connection
          try {
            withTimeout(1500) { bluetoothDevice.createRfcommSocket(3).connect() }
          } catch (e: IOException) {
            // ignore
          }
        } else {
          if (bluetoothDevice.bondState == BOND_BONDED) {
            // already bonded, just reconnect
            bluetoothDevice.connect()
@@ -284,16 +294,15 @@ class Host(private val context: Context, private val server: Server) : HostImplB
          } else {
            // need to bond
            bluetoothDevice.createBond()
            if (!request.manuallyConfirm) {
              acceptPairingAndAwaitBonded(bluetoothDevice)
            }
          }
        }
      }

      ConnectResponse.newBuilder()
        .setConnection(
          Connection.newBuilder()
            .setCookie(ByteString.copyFromUtf8(bluetoothDevice.address))
            .build()
        )
        .setConnection(newConnection(bluetoothDevice, Transport.TRANSPORT_BREDR))
        .build()
    }
  }
@@ -333,9 +342,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB
      val device = scanLeDevice(address)
      GattInstance(device!!, TRANSPORT_LE, context).waitForState(BluetoothProfile.STATE_CONNECTED)
      ConnectLEResponse.newBuilder()
        .setConnection(
          Connection.newBuilder().setCookie(ByteString.copyFromUtf8(device.address)).build()
        )
        .setConnection(newConnection(device, Transport.TRANSPORT_LE))
        .build()
    }
  }
@@ -351,9 +358,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB
        bluetoothAdapter.getRemoteLeDevice(address, BluetoothDevice.ADDRESS_TYPE_PUBLIC)
      if (device.isConnected) {
        GetLEConnectionResponse.newBuilder()
          .setConnection(
            Connection.newBuilder().setCookie(ByteString.copyFromUtf8(device.address)).build()
          )
          .setConnection(newConnection(device, Transport.TRANSPORT_LE))
          .build()
      } else {
        Log.e(TAG, "Device: $device is not connected")
@@ -364,7 +369,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB

  override fun disconnectLE(request: DisconnectLERequest, responseObserver: StreamObserver<Empty>) {
    grpcUnary<Empty>(scope, responseObserver) {
      val address = request.connection.cookie.toByteArray().decodeToString()
      val address = request.connection.address
      Log.i(TAG, "disconnectLE: $address")
      val gattInstance = GattInstance.get(address)

@@ -432,9 +437,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB
            .addDevice(
              Device.newBuilder()
                .setName(device.name)
                .setAddress(
                  ByteString.copyFrom(MacAddress.fromString(device.address).toByteArray())
                )
                .setAddress(device.toByteString())
            )
            .build()
        }
+12 −13
Original line number Diff line number Diff line
@@ -125,15 +125,14 @@ class Security(private val context: Context) : SecurityImplBase() {
        }
        .launchIn(this)

      flow.map { intent ->
      flow
        .filter { intent -> intent.action == ACTION_PAIRING_REQUEST }
        .map { intent ->
          val device = intent.getBluetoothDeviceExtra()
          val variant = intent.getIntExtra(EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR)
        Log.i(
          TAG,
          "OnPairing: Handling PairingEvent ${variant} for device ${device.address}"
        )
          Log.i(TAG, "OnPairing: Handling PairingEvent ${variant} for device ${device.address}")
          val eventBuilder =
          PairingEvent.newBuilder().setAddress(ByteString.copyFrom(device.toByteArray()))
            PairingEvent.newBuilder().setAddress(device.toByteString())
          when (variant) {
            // SSP / LE Just Works
            BluetoothDevice.PAIRING_VARIANT_CONSENT ->
+23 −3
Original line number Diff line number Diff line
@@ -53,6 +53,8 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import pandora.HostProto.Connection
import pandora.HostProto.InternalConnectionRef
import pandora.HostProto.Transport

fun shell(cmd: String): String {
  val fd = InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd)
@@ -284,7 +286,7 @@ fun <T> getProfileProxy(context: Context, profile: Int): T {
}

fun Intent.getBluetoothDeviceExtra(): BluetoothDevice =
  this.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
  this.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)!!

fun ByteString.decodeAsMacAddressToString(): String =
  MacAddress.fromBytes(this.toByteArray()).toString().uppercase()
@@ -293,6 +295,24 @@ fun ByteString.toBluetoothDevice(adapter: BluetoothAdapter): BluetoothDevice =
  adapter.getRemoteDevice(this.decodeAsMacAddressToString())

fun Connection.toBluetoothDevice(adapter: BluetoothAdapter): BluetoothDevice =
  adapter.getRemoteDevice(this.cookie.toByteArray().decodeToString())
  adapter.getRemoteDevice(address)

fun BluetoothDevice.toByteArray(): ByteArray = MacAddress.fromString(this.address).toByteArray()
val Connection.address: String
  get() = InternalConnectionRef.parseFrom(this.cookie).address.decodeAsMacAddressToString()

val Connection.transport: Transport
  get() = InternalConnectionRef.parseFrom(this.cookie).transport

fun newConnection(device: BluetoothDevice, transport: Transport) =
  Connection.newBuilder()
    .setCookie(
      InternalConnectionRef.newBuilder()
        .setAddress(device.toByteString())
        .setTransport(transport)
        .build()
        .toByteString()
    )
    .build()!!

fun BluetoothDevice.toByteString() =
  ByteString.copyFrom(MacAddress.fromString(this.address).toByteArray())!!