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

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

Merge changes Iabe58600,Ie1f2c0dd am: 2e97a8c7

parents 13f3517c 2e97a8c7
Loading
Loading
Loading
Loading
+22 −0
Original line number Original line Diff line number Diff line
@@ -34,6 +34,9 @@ service GATT {


  // Reads characteristic with given descriptor handle.
  // Reads characteristic with given descriptor handle.
  rpc ReadCharacteristicDescriptorFromHandle(ReadCharacteristicDescriptorRequest) returns (ReadCharacteristicDescriptorResponse);
  rpc ReadCharacteristicDescriptorFromHandle(ReadCharacteristicDescriptorRequest) returns (ReadCharacteristicDescriptorResponse);

  // Register a GATT service
  rpc RegisterService(RegisterServiceRequest) returns (RegisterServiceResponse);
}
}


enum AttStatusCode {
enum AttStatusCode {
@@ -172,3 +175,22 @@ message ReadCharacteristicDescriptorResponse {
  AttValue value = 1;
  AttValue value = 1;
  AttStatusCode status = 2;
  AttStatusCode status = 2;
}
}

message GattServiceParams {
  string uuid = 1;
  repeated GattCharacteristicParams characteristics = 2;
}

message GattCharacteristicParams {
  uint32 properties = 1;
  uint32 permissions = 2;
  string uuid = 3;
}

message RegisterServiceRequest {
  GattServiceParams service = 1;
}

message RegisterServiceResponse {
  GattService service = 1;
}
+24 −0
Original line number Original line Diff line number Diff line
@@ -43,6 +43,10 @@ service Host {
  rpc DisconnectLE(DisconnectLERequest) returns (google.protobuf.Empty);
  rpc DisconnectLE(DisconnectLERequest) returns (google.protobuf.Empty);
  // Start LE advertisement
  // Start LE advertisement
  rpc SetLEConnectable(google.protobuf.Empty) returns (google.protobuf.Empty);
  rpc SetLEConnectable(google.protobuf.Empty) returns (google.protobuf.Empty);
  // Run BR/EDR inquiry and returns each device found
  rpc RunInquiry(RunInquiryRequest) returns (stream RunInquiryResponse);
  // Run LE discovery (scanning) and return each device found
  rpc RunDiscovery(RunDiscoveryRequest) returns (stream RunDiscoveryResponse);
}
}


// Response of the `ReadLocalAddress` method.
// Response of the `ReadLocalAddress` method.
@@ -141,3 +145,23 @@ message GetLEConnectionResponse {
message DisconnectLERequest {
message DisconnectLERequest {
  Connection connection = 1;
  Connection connection = 1;
}
}

message RunInquiryRequest {
}

message RunInquiryResponse {
  repeated Device device = 1;
}

message RunDiscoveryRequest {
}

message RunDiscoveryResponse {
  Device device = 1;
  uint32 flags = 2;
}

message Device {
  string name = 1;
  bytes address = 2;
}
+120 −100
Original line number Original line Diff line number Diff line
@@ -20,18 +20,20 @@ import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothGattService.SERVICE_TYPE_PRIMARY
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Context
import android.content.Intent
import android.content.Intent
import android.content.IntentFilter
import android.content.IntentFilter
import android.util.Log
import android.util.Log
import com.google.protobuf.Empty
import io.grpc.Status
import io.grpc.Status
import io.grpc.stub.StreamObserver
import io.grpc.stub.StreamObserver
import java.util.UUID
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted
@@ -45,15 +47,15 @@ import pandora.GattProto.*
class Gatt(private val context: Context) : GATTImplBase() {
class Gatt(private val context: Context) : GATTImplBase() {
  private val TAG = "PandoraGatt"
  private val TAG = "PandoraGatt"


  private val mScope: CoroutineScope
  private val mScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
  private val flow: Flow<Intent>


  private val mBluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
  private val mBluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
  private val mBluetoothAdapter = mBluetoothManager.adapter
  private val mBluetoothAdapter = mBluetoothManager.adapter


  init {
  private val serverManager by lazy { GattServerManager(mBluetoothManager, context, mScope) }
    mScope = CoroutineScope(Dispatchers.Default)


  private val flow: Flow<Intent>
  init {
    val intentFilter = IntentFilter()
    val intentFilter = IntentFilter()
    intentFilter.addAction(BluetoothDevice.ACTION_UUID)
    intentFilter.addAction(BluetoothDevice.ACTION_UUID)


@@ -61,10 +63,14 @@ class Gatt(private val context: Context) : GATTImplBase() {
  }
  }


  fun deinit() {
  fun deinit() {
    serverManager.server.close()
    mScope.cancel()
    mScope.cancel()
  }
  }


  override fun exchangeMTU(request: ExchangeMTURequest, responseObserver: StreamObserver<ExchangeMTUResponse>) {
  override fun exchangeMTU(
      request: ExchangeMTURequest,
      responseObserver: StreamObserver<ExchangeMTUResponse>
  ) {
    grpcUnary<ExchangeMTUResponse>(mScope, responseObserver) {
    grpcUnary<ExchangeMTUResponse>(mScope, responseObserver) {
      val mtu = request.mtu
      val mtu = request.mtu
      Log.i(TAG, "exchangeMTU MTU=$mtu")
      Log.i(TAG, "exchangeMTU MTU=$mtu")
@@ -88,24 +94,16 @@ class Gatt(private val context: Context) : GATTImplBase() {
      if (characteristic == null) {
      if (characteristic == null) {
        val descriptor: BluetoothGattDescriptor? =
        val descriptor: BluetoothGattDescriptor? =
            getDescriptorWithHandle(request.handle, gattInstance)
            getDescriptorWithHandle(request.handle, gattInstance)
        checkNotNull(descriptor) { "Found no characteristic or descriptor with handle ${request.handle}" }
        checkNotNull(descriptor) {
        val valueWrote = gattInstance.writeDescriptorBlocking(
          "Found no characteristic or descriptor with handle ${request.handle}"
          descriptor,
        }
          request.value.toByteArray()
        val valueWrote =
        )
            gattInstance.writeDescriptorBlocking(descriptor, request.value.toByteArray())
        WriteResponse.newBuilder()
        WriteResponse.newBuilder().setHandle(valueWrote.handle).setStatus(valueWrote.status).build()
          .setHandle(valueWrote.handle)
          .setStatus(valueWrote.status)
          .build()
      } else {
      } else {
        val valueWrote = gattInstance.writeCharacteristicBlocking(
        val valueWrote =
          characteristic,
            gattInstance.writeCharacteristicBlocking(characteristic, request.value.toByteArray())
          request.value.toByteArray()
        WriteResponse.newBuilder().setHandle(valueWrote.handle).setStatus(valueWrote.status).build()
        )
        WriteResponse.newBuilder()
          .setHandle(valueWrote.handle)
          .setStatus(valueWrote.status)
          .build()
      }
      }
    }
    }
  }
  }
@@ -164,7 +162,10 @@ class Gatt(private val context: Context) : GATTImplBase() {
    }
    }
  }
  }


  override fun clearCache(request: ClearCacheRequest, responseObserver: StreamObserver<ClearCacheResponse>) {
  override fun clearCache(
      request: ClearCacheRequest,
      responseObserver: StreamObserver<ClearCacheResponse>
  ) {
    grpcUnary<ClearCacheResponse>(mScope, responseObserver) {
    grpcUnary<ClearCacheResponse>(mScope, responseObserver) {
      Log.i(TAG, "clearCache")
      Log.i(TAG, "clearCache")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val gattInstance = GattInstance.get(request.connection.cookie)
@@ -185,11 +186,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
      checkNotNull(characteristic) { "Characteristic handle ${request.handle} not found." }
      checkNotNull(characteristic) { "Characteristic handle ${request.handle} not found." }
      val readValue = gattInstance.readCharacteristicBlocking(characteristic)
      val readValue = gattInstance.readCharacteristicBlocking(characteristic)
      ReadCharacteristicResponse.newBuilder()
      ReadCharacteristicResponse.newBuilder()
        .setValue(
          .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
          AttValue.newBuilder()
            .setHandle(readValue.handle)
            .setValue(readValue.value)
        )
          .setStatus(readValue.status)
          .setStatus(readValue.status)
          .build()
          .build()
    }
    }
@@ -205,10 +202,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
      tryDiscoverServices(gattInstance)
      tryDiscoverServices(gattInstance)
      val readValues =
      val readValues =
          gattInstance.readCharacteristicUuidBlocking(
          gattInstance.readCharacteristicUuidBlocking(
          UUID.fromString(request.uuid),
              UUID.fromString(request.uuid), request.startHandle, request.endHandle)
          request.startHandle,
          request.endHandle
        )
      ReadCharacteristicsFromUuidResponse.newBuilder()
      ReadCharacteristicsFromUuidResponse.newBuilder()
          .addAllCharacteristicsRead(generateReadValuesList(readValues))
          .addAllCharacteristicsRead(generateReadValuesList(readValues))
          .build()
          .build()
@@ -227,16 +221,46 @@ class Gatt(private val context: Context) : GATTImplBase() {
      checkNotNull(descriptor) { "Descriptor handle ${request.handle} not found." }
      checkNotNull(descriptor) { "Descriptor handle ${request.handle} not found." }
      val readValue = gattInstance.readDescriptorBlocking(descriptor)
      val readValue = gattInstance.readDescriptorBlocking(descriptor)
      ReadCharacteristicDescriptorResponse.newBuilder()
      ReadCharacteristicDescriptorResponse.newBuilder()
        .setValue(
          .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
          AttValue.newBuilder()
            .setHandle(readValue.handle)
            .setValue(readValue.value)
        )
          .setStatus(readValue.status)
          .setStatus(readValue.status)
          .build()
          .build()
    }
    }
  }
  }


  override fun registerService(
      request: RegisterServiceRequest,
      responseObserver: StreamObserver<RegisterServiceResponse>
  ) {
    grpcUnary(mScope, responseObserver) {
      val service =
          BluetoothGattService(UUID.fromString(request.service.uuid), SERVICE_TYPE_PRIMARY)
      for (characteristic in request.service.characteristicsList) {
        service.addCharacteristic(
            BluetoothGattCharacteristic(
                UUID.fromString(characteristic.uuid),
                characteristic.properties,
                characteristic.permissions))
      }

      val fullService = coroutineScope {
        val firstService = mScope.async { serverManager.newServiceFlow.first() }
        serverManager.server.addService(service)
        firstService.await()
      }

      RegisterServiceResponse.newBuilder()
          .setService(
              GattService.newBuilder()
                  .setHandle(fullService.instanceId)
                  .setType(fullService.type)
                  .setUuid(fullService.uuid.toString())
                  .addAllIncludedServices(generateServicesList(service.includedServices, 1))
                  .addAllCharacteristics(generateCharacteristicsList(service.characteristics))
                  .build())
          .build()
    }
  }

  /**
  /**
   * Discovers services, then returns characteristic with given handle. BluetoothGatt API is
   * Discovers services, then returns characteristic with given handle. BluetoothGatt API is
   * package-private so we have to redefine it here.
   * package-private so we have to redefine it here.
@@ -338,11 +362,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
    for (readValue in readValuesList) {
    for (readValue in readValuesList) {
      val readValueBuilder =
      val readValueBuilder =
          ReadCharacteristicResponse.newBuilder()
          ReadCharacteristicResponse.newBuilder()
        .setValue(
              .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
          AttValue.newBuilder()
            .setHandle(readValue.handle)
            .setValue(readValue.value)
        )
              .setStatus(readValue.status)
              .setStatus(readValue.status)
      newReadValuesList.add(readValueBuilder.build())
      newReadValuesList.add(readValueBuilder.build())
    }
    }
+53 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.pandora

import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattServer
import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.content.Context
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map

class GattServerManager(bluetoothManager: BluetoothManager, context: Context, globalScope: CoroutineScope) {
  val services = mutableMapOf<UUID, BluetoothGattService>()
  val server: BluetoothGattServer

  val newServiceFlow = MutableSharedFlow<BluetoothGattService>(extraBufferCapacity = 8)

  init {
    newServiceFlow.map {
      services[it.uuid] = it
    }.launchIn(globalScope)
  }

  init {
    val callback =
      object : BluetoothGattServerCallback() {
        override fun onServiceAdded(status: Int, service: BluetoothGattService) {
          check(status == BluetoothGatt.GATT_SUCCESS)
          check(newServiceFlow.tryEmit(service))
        }
      }
    server = bluetoothManager.openGattServer(context, callback)
  }
}
+72 −0
Original line number Original line Diff line number Diff line
@@ -42,8 +42,10 @@ import kotlin.Result.Companion.success
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.delay
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
@@ -77,6 +79,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB
    intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
    intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
    intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
    intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
    intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
    intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
    intentFilter.addAction(BluetoothDevice.ACTION_FOUND)


    // Creates a shared flow of intents that can be used in all methods in the coroutine scope.
    // Creates a shared flow of intents that can be used in all methods in the coroutine scope.
    // This flow is started eagerly to make sure that the broadcast receiver is registered before
    // This flow is started eagerly to make sure that the broadcast receiver is registered before
@@ -405,4 +408,73 @@ class Host(private val context: Context, private val server: Server) : HostImplB
    }
    }
    return bluetoothDevice
    return bluetoothDevice
  }
  }

  override fun runInquiry(
    request: RunInquiryRequest,
    responseObserver: StreamObserver<RunInquiryResponse>
  ) {
    Log.d(TAG, "runInquiry")
    grpcServerStream(scope, responseObserver) {
      launch {
        try {
          bluetoothAdapter.startDiscovery()
          awaitCancellation()
        } finally {
          bluetoothAdapter.cancelDiscovery()
        }
      }
      flow
        .filter { it.action == BluetoothDevice.ACTION_FOUND }
        .map {
          val device = it.getBluetoothDeviceExtra()
          Log.i(TAG, "Device found: $device")
          RunInquiryResponse.newBuilder()
            .addDevice(
              Device.newBuilder()
                .setName(device.name)
                .setAddress(
                  ByteString.copyFrom(MacAddress.fromString(device.address).toByteArray())
                )
            )
            .build()
        }
    }
  }

  override fun runDiscovery(
    request: RunDiscoveryRequest,
    responseObserver: StreamObserver<RunDiscoveryResponse>
  ) {
    Log.d(TAG, "runDiscovery")
    grpcServerStream(scope, responseObserver) {
      callbackFlow {
        val callback =
          object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
              sendBlocking(
                RunDiscoveryResponse.newBuilder()
                  .setDevice(
                    Device.newBuilder()
                      .setAddress(
                        ByteString.copyFrom(
                          MacAddress.fromString(result.device.address).toByteArray()
                        )
                      )
                      .setName(result.device.name ?: "")
                  )
                  .setFlags(result.scanRecord?.advertiseFlags ?: 0)
                  .build()
              )
            }

            override fun onScanFailed(errorCode: Int) {
              error("scan failed")
            }
          }
        bluetoothAdapter.bluetoothLeScanner.startScan(callback)

        awaitClose { bluetoothAdapter.bluetoothLeScanner.stopScan(callback) }
      }
    }
  }
}
}
Loading