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

Commit edcef622 authored by Rahul Arya's avatar Rahul Arya Committed by Thomas Girardier
Browse files

[Pandora] GATT server interfaces

Tag: #feature
Bug: 245578454
Test: Test: GAP PTS tests in future CL
Ignore-AOSP-First: Cherry-pick from AOSP
Merged-In: Ie1f2c0dda7d6713194ac689718127010dd22fcfd
Change-Id: Ie1f2c0dda7d6713194ac689718127010dd22fcfd
parent d8bef151
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -34,6 +34,9 @@ service GATT {

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

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

enum AttStatusCode {
@@ -172,3 +175,22 @@ message ReadCharacteristicDescriptorResponse {
  AttValue value = 1;
  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;
}
+120 −100
Original line number Diff line number Diff line
@@ -20,18 +20,20 @@ import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothGattService.SERVICE_TYPE_PRIMARY
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import com.google.protobuf.Empty
import io.grpc.Status
import io.grpc.stub.StreamObserver
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -45,15 +47,15 @@ import pandora.GattProto.*
class Gatt(private val context: Context) : GATTImplBase() {
  private val TAG = "PandoraGatt"

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

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

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

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

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

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

  override fun exchangeMTU(request: ExchangeMTURequest, responseObserver: StreamObserver<ExchangeMTUResponse>) {
  override fun exchangeMTU(
      request: ExchangeMTURequest,
      responseObserver: StreamObserver<ExchangeMTUResponse>
  ) {
    grpcUnary<ExchangeMTUResponse>(mScope, responseObserver) {
      val mtu = request.mtu
      Log.i(TAG, "exchangeMTU MTU=$mtu")
@@ -88,24 +94,16 @@ class Gatt(private val context: Context) : GATTImplBase() {
      if (characteristic == null) {
        val descriptor: BluetoothGattDescriptor? =
            getDescriptorWithHandle(request.handle, gattInstance)
        checkNotNull(descriptor) { "Found no characteristic or descriptor with handle ${request.handle}" }
        val valueWrote = gattInstance.writeDescriptorBlocking(
          descriptor,
          request.value.toByteArray()
        )
        WriteResponse.newBuilder()
          .setHandle(valueWrote.handle)
          .setStatus(valueWrote.status)
          .build()
        checkNotNull(descriptor) {
          "Found no characteristic or descriptor with handle ${request.handle}"
        }
        val valueWrote =
            gattInstance.writeDescriptorBlocking(descriptor, request.value.toByteArray())
        WriteResponse.newBuilder().setHandle(valueWrote.handle).setStatus(valueWrote.status).build()
      } else {
        val valueWrote = gattInstance.writeCharacteristicBlocking(
          characteristic,
          request.value.toByteArray()
        )
        WriteResponse.newBuilder()
          .setHandle(valueWrote.handle)
          .setStatus(valueWrote.status)
          .build()
        val valueWrote =
            gattInstance.writeCharacteristicBlocking(characteristic, request.value.toByteArray())
        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) {
      Log.i(TAG, "clearCache")
      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." }
      val readValue = gattInstance.readCharacteristicBlocking(characteristic)
      ReadCharacteristicResponse.newBuilder()
        .setValue(
          AttValue.newBuilder()
            .setHandle(readValue.handle)
            .setValue(readValue.value)
        )
          .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
          .setStatus(readValue.status)
          .build()
    }
@@ -205,10 +202,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
      tryDiscoverServices(gattInstance)
      val readValues =
          gattInstance.readCharacteristicUuidBlocking(
          UUID.fromString(request.uuid),
          request.startHandle,
          request.endHandle
        )
              UUID.fromString(request.uuid), request.startHandle, request.endHandle)
      ReadCharacteristicsFromUuidResponse.newBuilder()
          .addAllCharacteristicsRead(generateReadValuesList(readValues))
          .build()
@@ -227,16 +221,46 @@ class Gatt(private val context: Context) : GATTImplBase() {
      checkNotNull(descriptor) { "Descriptor handle ${request.handle} not found." }
      val readValue = gattInstance.readDescriptorBlocking(descriptor)
      ReadCharacteristicDescriptorResponse.newBuilder()
        .setValue(
          AttValue.newBuilder()
            .setHandle(readValue.handle)
            .setValue(readValue.value)
        )
          .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
          .setStatus(readValue.status)
          .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
   * package-private so we have to redefine it here.
@@ -338,11 +362,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
    for (readValue in readValuesList) {
      val readValueBuilder =
          ReadCharacteristicResponse.newBuilder()
        .setValue(
          AttValue.newBuilder()
            .setHandle(readValue.handle)
            .setValue(readValue.value)
        )
              .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
              .setStatus(readValue.status)
      newReadValuesList.add(readValueBuilder.build())
    }
+53 −0
Original line number 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)
  }
}