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

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

[Pandora] Add RunInquiry Host interface

This is so we can discover classic devices. RootCanal doesn't support
this fully yet (I think?) but I will add the needed support for tests
that require inquiry to work.

Tag: #feature
Bug: 245578454
Test: Test: next CL
Ignore-AOSP-First: Cherry-pick from AOSP
Merged-In: Iabe58600b50f6f3f5ab116bb1d7844a1bd07c9e9
Change-Id: Iabe58600b50f6f3f5ab116bb1d7844a1bd07c9e9
parent edcef622
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -43,6 +43,10 @@ service Host {
  rpc DisconnectLE(DisconnectLERequest) returns (google.protobuf.Empty);
  // Start LE advertisement
  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.
@@ -141,3 +145,23 @@ message GetLEConnectionResponse {
message DisconnectLERequest {
  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;
}
+72 −0
Original line number Diff line number Diff line
@@ -42,8 +42,10 @@ import kotlin.Result.Companion.success
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.delay
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(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
    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.
    // 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
  }

  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) }
      }
    }
  }
}
+51 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.google.protobuf.ByteString
import io.grpc.stub.ServerCallStreamObserver
import io.grpc.stub.StreamObserver
import java.io.BufferedReader
import java.io.InputStreamReader
@@ -137,7 +138,6 @@ fun <T> grpcUnary(
 * Example usage:
 * ```
 * override fun grpcMethod(
 *   request: TypeOfRequest,
 *   responseObserver: StreamObserver<TypeOfResponse> {
 *     grpcBidirectionalStream(scope, responseObserver) {
 *       block
@@ -195,6 +195,56 @@ fun <T, U> grpcBidirectionalStream(
  }
}

/**
 * Creates a gRPC coroutine in a given coroutine scope which executes a given suspended function
 * taking in a Flow of gRPC requests and returning a Flow of gRPC responses and sends it on a given
 * gRPC stream observer.
 *
 * @param T the type of gRPC response.
 * @param scope coroutine scope used to run the coroutine.
 * @param responseObserver the gRPC stream observer on which to send the response.
 * @param block the suspended function producing the response Flow.
 * @return a StreamObserver for the incoming requests.
 *
 * Example usage:
 * ```
 * override fun grpcMethod(
 *   request: TypeOfRequest,
 *   responseObserver: StreamObserver<TypeOfResponse> {
 *     grpcServerStream(scope, responseObserver) {
 *       block
 *     }
 *   }
 * }
 * ```
 */
@kotlinx.coroutines.ExperimentalCoroutinesApi
fun <T> grpcServerStream(
  scope: CoroutineScope,
  responseObserver: StreamObserver<T>,
  block: CoroutineScope.() -> Flow<T>
) {
  val serverCallStreamObserver = responseObserver as ServerCallStreamObserver<T>

  val job =
    scope.launch {
      block()
        .onEach { responseObserver.onNext(it) }
        .onCompletion { error ->
          if (error == null) {
            responseObserver.onCompleted()
          }
        }
        .catch {
          it.printStackTrace()
          responseObserver.onError(it)
        }
        .launchIn(this)
    }

  serverCallStreamObserver.setOnCancelHandler { job.cancel() }
}

/**
 * Synchronous method to get a Bluetooth profile proxy.
 *