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

Commit 6e22bfea authored by Rahul Arya's avatar Rahul Arya
Browse files

[Pandora] Implementing advertising interfaces

Based on discussion in aosp/2193570, moving to this CL because of merge
conflicts + lazy.

Test: GAP PTS tests in future CLs
Bug: 245007199
Tag: #feature
Change-Id: I3ac8a0e2d735148ec5628e230149544cb25c2e8f
parent b120a722
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
import time
import sys

from mmi2grpc._helpers import assert_description
from mmi2grpc._helpers import match_description
from mmi2grpc._proxy import ProfileProxy

from pandora_experimental.host_grpc import Host
from pandora_experimental.host_pb2 import Connection
from pandora_experimental.host_pb2 import Connection, ConnectabilityMode, AddressType
from pandora_experimental.l2cap_grpc import L2CAP

from typing import Optional
import sys


class L2CAPProxy(ProfileProxy):
@@ -88,7 +90,10 @@ class L2CAPProxy(ProfileProxy):
        """
        Place the IUT into LE connectable mode.
        """
        self.host.SetLEConnectable()
        self.host.StartAdvertising(
            connectability_mode=ConnectabilityMode.CONECTABILITY_CONNECTABLE,
            own_address_type=AddressType.PUBLIC,
        )
        # not strictly necessary, but can save time on waiting connection
        tests_to_open_bluetooth_server_socket = [
            "L2CAP/LE/CFC/BV-03-C",
+5 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ from mmi2grpc._streaming import StreamWrapper

from pandora_experimental.security_grpc import Security
from pandora_experimental.host_grpc import Host
from pandora_experimental.host_pb2 import ConnectabilityMode, AddressType


def debug(*args, **kwargs):
@@ -89,7 +90,10 @@ class SMProxy(ProfileProxy):
        """
        Action: Place the IUT in connectable mode
        """
        self.host.SetLEConnectable()
        self.host.StartAdvertising(
            connectability_mode=ConnectabilityMode.CONECTABILITY_CONNECTABLE,
            own_address_type=AddressType.PUBLIC,
        )
        return "OK"

    @assert_description
+75 −2
Original line number Diff line number Diff line
@@ -41,8 +41,13 @@ service Host {
  rpc GetLEConnection(GetLEConnectionRequest) returns (GetLEConnectionResponse);
  // Disconnect ongoing LE connection.
  rpc DisconnectLE(DisconnectLERequest) returns (google.protobuf.Empty);
  // Start LE advertisement
  rpc SetLEConnectable(google.protobuf.Empty) returns (google.protobuf.Empty);
  // Create and enable an advertising set using legacy or extended advertising,
  // except periodic advertising.
  rpc StartAdvertising(StartAdvertisingRequest) returns (StartAdvertisingResponse);
  // Create and enable a periodic advertising set.
  rpc StartPeriodicAdvertising(StartPeriodicAdvertisingRequest) returns (StartPeriodicAdvertisingResponse);
  // Remove an advertising set.
  rpc StopAdvertising(StopAdvertisingRequest) returns (StopAdvertisingResponse);
  // Run BR/EDR inquiry and returns each device found
  rpc RunInquiry(RunInquiryRequest) returns (stream RunInquiryResponse);
  // Run LE discovery (scanning) and return each device found
@@ -168,6 +173,74 @@ message DisconnectLERequest {
  Connection connection = 1;
}

message AdvertisingHandle {
  bytes cookie = 1;
}

enum AddressType {
  PUBLIC = 0x00;
  RANDOM = 0x01;
}

// Advertising Data including one or multiple AD types.
// Since the Flags AD type is mandatory, it must be automatically set by the
// IUT.
// include_<AD type> fields are used for AD type which are generally not
// exposed and that must be set by the IUT when specified.
// See Core Supplement, Part A, Data Types for details
message AdvertisingData {
  repeated string service_uuids = 1;
  bool include_local_name = 2;
  bytes manufacturer_specific_data = 3;
  bool include_tx_power_level = 4;
  bool include_peripheral_connection_interval_range = 5;
  repeated string service_solicitation = 6;
  map<string, bytes> service_data = 7;
  // Must be on 16 bits.
  uint32 appearance = 8;
  repeated bytes public_target_addresses = 9;
  repeated bytes random_target_addresses = 10;
  bool include_advertising_interval = 11;
  bool include_le_address = 12;
  bool include_le_role = 13;
  string uri = 14;
}

message StartAdvertisingRequest {
  bool legacy = 1;
  DiscoverabilityMode discovery_mode = 2;
  ConnectabilityMode connectability_mode = 3;
  AddressType own_address_type = 4;
  // If none, undirected.
  bytes peer_address = 5;
  AdvertisingData advertising_data = 6;
  // If none, not scannable.
  AdvertisingData scan_response_data = 7;
}

message StartAdvertisingResponse {
  AdvertisingHandle handle = 1;
}

message StartPeriodicAdvertisingRequest {
  AddressType own_address_type = 1;
  // If none, undirected.
  bytes peer_address = 2;
  uint32 interval_min = 3;
  uint32 interval_max = 4;
  AdvertisingData advertising_data = 5;
}

message StartPeriodicAdvertisingResponse {
  AdvertisingHandle handle = 1;
}

message StopAdvertisingRequest {
  AdvertisingHandle handle = 1;
}

message StopAdvertisingResponse {}

message RunInquiryRequest {
}

+71 −43
Original line number Diff line number Diff line
@@ -17,7 +17,10 @@
package com.android.pandora

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothAssignedNumbers
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.ADDRESS_TYPE_PUBLIC
import android.bluetooth.BluetoothDevice.ADDRESS_TYPE_RANDOM
import android.bluetooth.BluetoothDevice.BOND_BONDED
import android.bluetooth.BluetoothDevice.TRANSPORT_LE
import android.bluetooth.BluetoothManager
@@ -32,6 +35,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.MacAddress
import android.os.ParcelUuid
import android.util.Log
import com.google.protobuf.ByteString
import com.google.protobuf.Empty
@@ -67,7 +71,7 @@ import pandora.HostProto.*
@kotlinx.coroutines.ExperimentalCoroutinesApi
class Host(private val context: Context, private val server: Server) : HostImplBase() {
  private val TAG = "PandoraHost"
  private val ADVERTISEMENT_DURATION_MILLIS: Int = 10000

  private val scope: CoroutineScope
  private val flow: Flow<Intent>

@@ -77,6 +81,8 @@ class Host(private val context: Context, private val server: Server) : HostImplB
  private var connectability = ConnectabilityMode.CONNECTABILITY_UNSPECIFIED
  private var discoverability = DiscoverabilityMode.DISCOVERABILITY_UNSPECIFIED

  private val advertisers = mutableMapOf<UUID, AdvertiseCallback>()

  init {
    scope = CoroutineScope(Dispatchers.Default)

@@ -231,48 +237,6 @@ class Host(private val context: Context, private val server: Server) : HostImplB
    }
  }

  /**
   * Set the device in advertisement mode for #ADVERTISEMENT_DURATION_MILLIS milliseconds.
   * @param request Request sent by the client.
   * @param responseObserver Response to build and set back to the client.
   */
  override fun setLEConnectable(
    request: Empty,
    responseObserver: StreamObserver<Empty>,
  ) {
    // Creates a gRPC coroutine in a given coroutine scope which executes a given suspended function
    // returning a gRPC response and sends it on a given gRPC stream observer.
    grpcUnary<Empty>(scope, responseObserver) {
      Log.i(TAG, "setLEConnectable")
      val advertiser = bluetoothAdapter.getBluetoothLeAdvertiser()
      val advertiseSettings =
        AdvertiseSettings.Builder()
          .setConnectable(true)
          .setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC)
          .setTimeout(ADVERTISEMENT_DURATION_MILLIS)
          .build()
      val advertiseData = AdvertiseData.Builder().build()
      suspendCoroutine<Boolean> { continuation ->
        val advertiseCallback =
          object : AdvertiseCallback() {
            override fun onStartFailure(errorCode: Int) {
              Log.i(TAG, "Advertising failed: $errorCode")
              continuation.resumeWith(failure(Exception("Advertising failed: $errorCode")))
            }

            override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
              Log.i(TAG, "Advertising success")
              continuation.resumeWith(success(true))
            }
          }
        advertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback)
      }

      // Response sent to client
      Empty.getDefaultInstance()
    }
  }

  override fun connect(request: ConnectRequest, responseObserver: StreamObserver<ConnectResponse>) {
    grpcUnary(scope, responseObserver) {
      val bluetoothDevice = request.address.toBluetoothDevice(bluetoothAdapter)
@@ -416,6 +380,70 @@ class Host(private val context: Context, private val server: Server) : HostImplB
    return bluetoothDevice
  }

  override fun startAdvertising(
    request: StartAdvertisingRequest,
    responseObserver: StreamObserver<StartAdvertisingResponse>
  ) {
    Log.d(TAG, "startAdvertising")
    grpcUnary(scope, responseObserver) {
      val handle = UUID.randomUUID()

      callbackFlow {
          val callback =
            object : AdvertiseCallback() {
              override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
                sendBlocking(
                  StartAdvertisingResponse.newBuilder()
                    .setHandle(
                      AdvertisingHandle.newBuilder()
                        .setCookie(ByteString.copyFromUtf8(handle.toString()))
                    )
                    .build()
                )
              }
              override fun onStartFailure(errorCode: Int) {
                error("failed to start advertising")
              }
            }

          advertisers[handle] = callback

          val advertisingDataBuilder = AdvertiseData.Builder()

          for (service_uuid in request.advertisingData.serviceUuidsList) {
            advertisingDataBuilder.addServiceUuid(ParcelUuid.fromString(service_uuid))
          }

          advertisingDataBuilder
            .setIncludeDeviceName(request.advertisingData.includeLocalName)
            .setIncludeTxPowerLevel(request.advertisingData.includeTxPowerLevel)
            .addManufacturerData(
              BluetoothAssignedNumbers.GOOGLE,
              request.advertisingData.manufacturerSpecificData.toByteArray()
            )

          bluetoothAdapter.bluetoothLeAdvertiser.startAdvertising(
            AdvertiseSettings.Builder()
              .setConnectable(request.connectabilityMode == ConnectabilityMode.CONECTABILITY_CONNECTABLE)
              .setOwnAddressType(
                when (request.ownAddressType!!) {
                  AddressType.PUBLIC -> ADDRESS_TYPE_PUBLIC
                  AddressType.RANDOM -> ADDRESS_TYPE_RANDOM
                  AddressType.UNRECOGNIZED ->
                    error("unrecognized address type ${request.ownAddressType}")
                }
              )
              .build(),
            advertisingDataBuilder.build(),
            callback,
          )

          awaitClose { /* no-op */}
        }
        .first()
    }
  }

  override fun runInquiry(
    request: RunInquiryRequest,
    responseObserver: StreamObserver<RunInquiryResponse>