1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.pandora 18 19 import android.bluetooth.BluetoothAdapter 20 import android.bluetooth.BluetoothAssignedNumbers 21 import android.bluetooth.BluetoothDevice 22 import android.bluetooth.BluetoothDevice.ADDRESS_TYPE_PUBLIC 23 import android.bluetooth.BluetoothDevice.BOND_BONDED 24 import android.bluetooth.BluetoothDevice.BOND_NONE 25 import android.bluetooth.BluetoothDevice.TRANSPORT_BREDR 26 import android.bluetooth.BluetoothDevice.TRANSPORT_LE 27 import android.bluetooth.BluetoothManager 28 import android.bluetooth.BluetoothProfile 29 import android.bluetooth.BluetoothUuid 30 import android.bluetooth.le.AdvertiseCallback 31 import android.bluetooth.le.AdvertiseData 32 import android.bluetooth.le.AdvertisingSet 33 import android.bluetooth.le.AdvertisingSetCallback 34 import android.bluetooth.le.AdvertisingSetParameters 35 import android.bluetooth.le.ScanCallback 36 import android.bluetooth.le.ScanRecord 37 import android.bluetooth.le.ScanResult 38 import android.bluetooth.le.ScanSettings 39 import android.content.Context 40 import android.content.Intent 41 import android.content.IntentFilter 42 import android.net.MacAddress 43 import android.os.ParcelUuid 44 import android.util.Log 45 import com.google.protobuf.ByteString 46 import com.google.protobuf.Empty 47 import io.grpc.stub.StreamObserver 48 import java.io.Closeable 49 import java.lang.IllegalArgumentException 50 import java.nio.ByteBuffer 51 import java.time.Duration 52 import java.util.UUID 53 import kotlinx.coroutines.CoroutineScope 54 import kotlinx.coroutines.Dispatchers 55 import kotlinx.coroutines.awaitCancellation 56 import kotlinx.coroutines.cancel 57 import kotlinx.coroutines.channels.awaitClose 58 import kotlinx.coroutines.channels.trySendBlocking 59 import kotlinx.coroutines.delay 60 import kotlinx.coroutines.flow.Flow 61 import kotlinx.coroutines.flow.SharingStarted 62 import kotlinx.coroutines.flow.callbackFlow 63 import kotlinx.coroutines.flow.filter 64 import kotlinx.coroutines.flow.first 65 import kotlinx.coroutines.flow.map 66 import kotlinx.coroutines.flow.shareIn 67 import kotlinx.coroutines.launch 68 import pandora.HostGrpc.HostImplBase 69 import pandora.HostProto.* 70 71 object ByteArrayOps { getUShortAtnull72 public fun getUShortAt(input: ByteArray, index: Int): UShort { 73 return (((input[index + 1].toUInt() and 0xffU) shl 8) or (input[index].toUInt() and 0xffU)) 74 .toUShort() 75 } 76 getShortAtnull77 public fun getShortAt(input: ByteArray, index: Int): Short { 78 return getUShortAt(input, index).toShort() 79 } 80 getUIntAtnull81 public fun getUIntAt(input: ByteArray, index: Int): UInt { 82 return (((input[index + 3].toUInt() and 0xffU) shl 24) or 83 ((input[index + 2].toUInt() and 0xffU) shl 16) or 84 ((input[index + 1].toUInt() and 0xffU) shl 8) or 85 (input[index].toUInt() and 0xffU)) 86 } 87 getIntAtnull88 public fun getIntAt(input: ByteArray, index: Int): Int { 89 return getUIntAt(input, index).toInt() 90 } 91 getUInt24Atnull92 public fun getUInt24At(input: ByteArray, index: Int): UInt { 93 return (((input[index + 2].toUInt() and 0xffU) shl 16) or 94 ((input[index + 1].toUInt() and 0xffU) shl 8) or 95 (input[index].toUInt() and 0xffU)) 96 } 97 getInt24Atnull98 public fun getInt24At(input: ByteArray, index: Int): Int { 99 return getUInt24At(input, index).toInt() 100 } 101 } 102 103 @kotlinx.coroutines.ExperimentalCoroutinesApi 104 class Host( 105 private val context: Context, 106 private val security: Security, 107 private val server: Server 108 ) : HostImplBase(), Closeable { 109 private val TAG = "PandoraHost" 110 111 private val scope: CoroutineScope 112 private val flow: Flow<Intent> 113 114 private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! 115 private val bluetoothAdapter = bluetoothManager.adapter 116 117 private var connectability = ConnectabilityMode.NOT_CONNECTABLE 118 private var discoverability = DiscoverabilityMode.NOT_DISCOVERABLE 119 120 private val advertisers = mutableMapOf<UUID, AdvertiseCallback>() 121 <lambda>null122 init { 123 scope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) 124 125 // Add all intent actions to be listened. 126 val intentFilter = IntentFilter() 127 intentFilter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED) 128 intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED) 129 intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED) 130 intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST) 131 intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED) 132 intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) 133 intentFilter.addAction(BluetoothDevice.ACTION_FOUND) 134 135 // Creates a shared flow of intents that can be used in all methods in the coroutine scope. 136 // This flow is started eagerly to make sure that the broadcast receiver is registered 137 // before 138 // any function call. This flow is only cancelled when the corresponding scope is cancelled. 139 flow = intentFlow(context, intentFilter, scope).shareIn(scope, SharingStarted.Eagerly) 140 } 141 closenull142 override fun close() { 143 scope.cancel() 144 } 145 rebootBluetoothnull146 private suspend fun rebootBluetooth() { 147 Log.i(TAG, "rebootBluetooth") 148 149 val stateFlow = 150 flow 151 .filter { it.getAction() == BluetoothAdapter.ACTION_BLE_STATE_CHANGED } 152 .map { it.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) } 153 154 if (bluetoothAdapter.isEnabled) { 155 bluetoothAdapter.disable() 156 stateFlow.filter { it == BluetoothAdapter.STATE_OFF }.first() 157 } 158 159 bluetoothAdapter.enable() 160 stateFlow.filter { it == BluetoothAdapter.STATE_ON }.first() 161 } 162 factoryResetnull163 override fun factoryReset(request: Empty, responseObserver: StreamObserver<Empty>) { 164 grpcUnary<Empty>(scope, responseObserver, timeout = 30) { 165 Log.i(TAG, "factoryReset") 166 167 // remove bond for each device to avoid auto connection if remote resets faster 168 for (device in bluetoothAdapter.bondedDevices) { 169 device.removeBond() 170 Log.i(TAG, "wait for remove bond to complete : device=$device") 171 flow 172 .filter { it.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED } 173 .filter { it.getBluetoothDeviceExtra() == device } 174 .map { 175 it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) 176 } 177 .filter { it == BOND_NONE } 178 .first() 179 } 180 181 val stateFlow = 182 flow 183 .filter { it.getAction() == BluetoothAdapter.ACTION_BLE_STATE_CHANGED } 184 .map { it.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) } 185 186 initiatedConnection.clear() 187 waitedAclConnection.clear() 188 waitedAclDisconnection.clear() 189 190 bluetoothAdapter.clearBluetooth() 191 192 stateFlow.filter { it == BluetoothAdapter.STATE_ON }.first() 193 // Delay to initialize the Bluetooth completely and to fix flakiness: b/266611263 194 delay(1000L) 195 Log.i(TAG, "Shutdown the gRPC Server") 196 server.shutdown() 197 198 // The last expression is the return value. 199 Empty.getDefaultInstance() 200 } 201 } 202 resetnull203 override fun reset(request: Empty, responseObserver: StreamObserver<Empty>) { 204 grpcUnary<Empty>(scope, responseObserver) { 205 Log.i(TAG, "reset") 206 initiatedConnection.clear() 207 waitedAclConnection.clear() 208 waitedAclDisconnection.clear() 209 210 rebootBluetooth() 211 212 Empty.getDefaultInstance() 213 } 214 } 215 readLocalAddressnull216 override fun readLocalAddress( 217 request: Empty, 218 responseObserver: StreamObserver<ReadLocalAddressResponse> 219 ) { 220 grpcUnary<ReadLocalAddressResponse>(scope, responseObserver) { 221 Log.i(TAG, "readLocalAddress") 222 val localMacAddress = MacAddress.fromString(bluetoothAdapter.getAddress()) 223 ReadLocalAddressResponse.newBuilder() 224 .setAddress(ByteString.copyFrom(localMacAddress.toByteArray())) 225 .build() 226 } 227 } 228 waitPairingRequestIntentnull229 private suspend fun waitPairingRequestIntent(bluetoothDevice: BluetoothDevice) { 230 Log.i(TAG, "waitPairingRequestIntent: device=$bluetoothDevice") 231 var pairingVariant = 232 flow 233 .filter { it.getAction() == BluetoothDevice.ACTION_PAIRING_REQUEST } 234 .filter { it.getBluetoothDeviceExtra() == bluetoothDevice } 235 .first() 236 .getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR) 237 238 val confirmationCases = 239 intArrayOf( 240 BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION, 241 BluetoothDevice.PAIRING_VARIANT_CONSENT, 242 BluetoothDevice.PAIRING_VARIANT_PIN, 243 ) 244 245 if (pairingVariant in confirmationCases) { 246 bluetoothDevice.setPairingConfirmation(true) 247 } 248 } 249 waitConnectionIntentnull250 private suspend fun waitConnectionIntent(bluetoothDevice: BluetoothDevice) { 251 Log.i(TAG, "waitConnectionIntent: device=$bluetoothDevice") 252 flow 253 .filter { it.action == BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED } 254 .filter { it.getBluetoothDeviceExtra() == bluetoothDevice } 255 .map { it.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.ERROR) } 256 .filter { it == BluetoothAdapter.STATE_CONNECTED } 257 .first() 258 } 259 waitBondIntentnull260 suspend fun waitBondIntent(bluetoothDevice: BluetoothDevice) { 261 // We only wait for bonding to be completed since we only need the ACL connection to be 262 // established with the peer device (on Android state connected is sent when all profiles 263 // have been connected). 264 Log.i(TAG, "waitBondIntent: device=$bluetoothDevice") 265 flow 266 .filter { it.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED } 267 .filter { it.getBluetoothDeviceExtra() == bluetoothDevice } 268 .map { it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) } 269 .filter { it == BOND_BONDED } 270 .first() 271 } 272 waitIncomingAclConnectedIntentnull273 suspend fun waitIncomingAclConnectedIntent(address: String?, transport: Int): Intent { 274 return flow 275 .filter { it.action == BluetoothDevice.ACTION_ACL_CONNECTED } 276 .filter { address == null || it.getBluetoothDeviceExtra().address == address } 277 .filter { !initiatedConnection.contains(it.getBluetoothDeviceExtra()) } 278 .filter { 279 it.getIntExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.ERROR) == transport 280 } 281 .first() 282 } 283 acceptPairingAndAwaitBondednull284 private suspend fun acceptPairingAndAwaitBonded(bluetoothDevice: BluetoothDevice) { 285 val acceptPairingJob = scope.launch { waitPairingRequestIntent(bluetoothDevice) } 286 waitBondIntent(bluetoothDevice) 287 if (acceptPairingJob.isActive) { 288 acceptPairingJob.cancel() 289 } 290 } 291 waitConnectionnull292 override fun waitConnection( 293 request: WaitConnectionRequest, 294 responseObserver: StreamObserver<WaitConnectionResponse> 295 ) { 296 grpcUnary(scope, responseObserver) { 297 if (request.address.isEmpty()) 298 throw IllegalArgumentException("Request address field must be set") 299 var bluetoothDevice = request.address.toBluetoothDevice(bluetoothAdapter) 300 301 Log.i(TAG, "waitConnection: device=$bluetoothDevice") 302 303 if (!bluetoothAdapter.isEnabled) { 304 throw RuntimeException("Bluetooth is not enabled, cannot waitConnection") 305 } 306 307 if (!bluetoothDevice.isConnected() || waitedAclConnection.contains(bluetoothDevice)) { 308 bluetoothDevice = 309 waitIncomingAclConnectedIntent(bluetoothDevice.address, TRANSPORT_BREDR) 310 .getBluetoothDeviceExtra() 311 } 312 313 waitedAclConnection.add(bluetoothDevice) 314 315 WaitConnectionResponse.newBuilder() 316 .setConnection(bluetoothDevice.toConnection(TRANSPORT_BREDR)) 317 .build() 318 } 319 } 320 waitDisconnectionnull321 override fun waitDisconnection( 322 request: WaitDisconnectionRequest, 323 responseObserver: StreamObserver<Empty> 324 ) { 325 grpcUnary(scope, responseObserver) { 326 val bluetoothDevice = request.connection.toBluetoothDevice(bluetoothAdapter) 327 Log.i(TAG, "waitDisconnection: device=$bluetoothDevice") 328 if (!bluetoothAdapter.isEnabled) { 329 throw RuntimeException("Bluetooth is not enabled, cannot waitDisconnection") 330 } 331 if ( 332 bluetoothDevice.isConnected() && !waitedAclDisconnection.contains(bluetoothDevice) 333 ) { 334 flow 335 .filter { it.action == BluetoothDevice.ACTION_ACL_DISCONNECTED } 336 .filter { it.getBluetoothDeviceExtra() == bluetoothDevice } 337 .first() 338 } 339 340 waitedAclDisconnection.add(bluetoothDevice) 341 342 Empty.getDefaultInstance() 343 } 344 } 345 connectnull346 override fun connect( 347 request: ConnectRequest, 348 responseObserver: StreamObserver<ConnectResponse> 349 ) { 350 grpcUnary(scope, responseObserver) { 351 if (request.address.isEmpty()) 352 throw IllegalArgumentException("Request address field must be set") 353 val bluetoothDevice = request.address.toBluetoothDevice(bluetoothAdapter) 354 355 Log.i(TAG, "connect: address=$bluetoothDevice") 356 357 initiatedConnection.add(bluetoothDevice) 358 bluetoothAdapter.cancelDiscovery() 359 360 if (!bluetoothDevice.isConnected()) { 361 if (bluetoothDevice.bondState == BOND_BONDED) { 362 // already bonded, just reconnect 363 bluetoothDevice.connect() 364 waitConnectionIntent(bluetoothDevice) 365 } else { 366 // need to bond 367 bluetoothDevice.createBond() 368 if (!security.manuallyConfirm) { 369 acceptPairingAndAwaitBonded(bluetoothDevice) 370 } 371 } 372 } 373 374 ConnectResponse.newBuilder() 375 .setConnection(bluetoothDevice.toConnection(TRANSPORT_BREDR)) 376 .build() 377 } 378 } 379 disconnectnull380 override fun disconnect(request: DisconnectRequest, responseObserver: StreamObserver<Empty>) { 381 grpcUnary<Empty>(scope, responseObserver) { 382 val bluetoothDevice = request.connection.toBluetoothDevice(bluetoothAdapter) 383 Log.i(TAG, "disconnect: device=$bluetoothDevice") 384 385 if (!bluetoothDevice.isConnected()) { 386 throw RuntimeException("Device is not connected, cannot disconnect") 387 } 388 389 when (request.connection.transport) { 390 TRANSPORT_BREDR -> { 391 Log.i(TAG, "disconnect BR_EDR") 392 bluetoothDevice.disconnect() 393 } 394 TRANSPORT_LE -> { 395 Log.i(TAG, "disconnect LE") 396 val gattInstance = 397 try { 398 GattInstance.get(bluetoothDevice.address) 399 } catch (e: Exception) { 400 Log.w(TAG, "Gatt instance doesn't exist. Android might be peripheral") 401 val instance = GattInstance(bluetoothDevice, TRANSPORT_LE, context) 402 instance.waitForState(BluetoothProfile.STATE_CONNECTED) 403 instance 404 } 405 if (gattInstance.isDisconnected()) { 406 throw RuntimeException("Device is not connected, cannot disconnect") 407 } 408 409 bluetoothDevice.disconnect() 410 gattInstance.disconnectInstance() 411 } 412 else -> { 413 throw RuntimeException("Device type UNKNOWN") 414 } 415 } 416 flow 417 .filter { it.action == BluetoothDevice.ACTION_ACL_DISCONNECTED } 418 .filter { it.getBluetoothDeviceExtra() == bluetoothDevice } 419 .first() 420 421 Empty.getDefaultInstance() 422 } 423 } 424 connectLEnull425 override fun connectLE( 426 request: ConnectLERequest, 427 responseObserver: StreamObserver<ConnectLEResponse> 428 ) { 429 grpcUnary<ConnectLEResponse>(scope, responseObserver) { 430 val ownAddressType = request.ownAddressType 431 if ( 432 ownAddressType != OwnAddressType.RANDOM && 433 ownAddressType != OwnAddressType.RESOLVABLE_OR_RANDOM 434 ) { 435 throw RuntimeException("connectLE: Unsupported OwnAddressType: $ownAddressType") 436 } 437 val (address, type) = 438 when (request.getAddressCase()!!) { 439 ConnectLERequest.AddressCase.PUBLIC -> 440 Pair(request.public, BluetoothDevice.ADDRESS_TYPE_PUBLIC) 441 ConnectLERequest.AddressCase.RANDOM -> 442 Pair(request.random, BluetoothDevice.ADDRESS_TYPE_RANDOM) 443 ConnectLERequest.AddressCase.PUBLIC_IDENTITY -> 444 Pair(request.publicIdentity, BluetoothDevice.ADDRESS_TYPE_PUBLIC) 445 ConnectLERequest.AddressCase.RANDOM_STATIC_IDENTITY -> 446 Pair(request.randomStaticIdentity, BluetoothDevice.ADDRESS_TYPE_RANDOM) 447 ConnectLERequest.AddressCase.ADDRESS_NOT_SET -> 448 throw IllegalArgumentException("Request address field must be set") 449 } 450 Log.i(TAG, "connectLE: $address") 451 val bluetoothDevice = 452 bluetoothAdapter.getRemoteLeDevice(address.decodeAsMacAddressToString(), type) 453 initiatedConnection.add(bluetoothDevice) 454 GattInstance(bluetoothDevice, TRANSPORT_LE, context) 455 .waitForState(BluetoothProfile.STATE_CONNECTED) 456 ConnectLEResponse.newBuilder() 457 .setConnection(bluetoothDevice.toConnection(TRANSPORT_LE)) 458 .build() 459 } 460 } 461 advertisenull462 override fun advertise( 463 request: AdvertiseRequest, 464 responseObserver: StreamObserver<AdvertiseResponse> 465 ) { 466 Log.d(TAG, "advertise") 467 grpcServerStream(scope, responseObserver) { 468 callbackFlow { 469 val callback = 470 object : AdvertisingSetCallback() { 471 override fun onAdvertisingSetStarted( 472 advertisingSet: AdvertisingSet, 473 txPower: Int, 474 status: Int 475 ) { 476 Log.d(TAG, "advertising started with status " + status) 477 if (status != 0) { 478 error("failed to start advertisingSet: $status") 479 } 480 } 481 } 482 val advertisingDataBuilder = AdvertiseData.Builder() 483 val dataTypesRequest = request.data 484 485 if ( 486 !dataTypesRequest.getIncompleteServiceClassUuids16List().isEmpty() or 487 !dataTypesRequest.getIncompleteServiceClassUuids32List().isEmpty() or 488 !dataTypesRequest.getIncompleteServiceClassUuids128List().isEmpty() 489 ) { 490 throw RuntimeException("Incomplete Service Class Uuids not supported") 491 } 492 493 // Handle service uuids 494 for (uuid16 in dataTypesRequest.getCompleteServiceClassUuids16List()) { 495 val parcel_uuid16 = 496 ParcelUuid.fromString("0000${uuid16}-0000-1000-8000-00805F9B34FB") 497 advertisingDataBuilder.addServiceUuid(parcel_uuid16) 498 } 499 for (uuid32 in dataTypesRequest.getCompleteServiceClassUuids32List()) { 500 val parcel_uuid32 = 501 ParcelUuid.fromString("${uuid32}-0000-1000-8000-00805F9B34FB") 502 advertisingDataBuilder.addServiceUuid(parcel_uuid32) 503 } 504 for (uuid128 in dataTypesRequest.getCompleteServiceClassUuids128List()) { 505 advertisingDataBuilder.addServiceUuid(ParcelUuid.fromString(uuid128)) 506 } 507 508 // Handle Service solicitation uuids 509 for (uuid16 in dataTypesRequest.getServiceSolicitationUuids16List()) { 510 val parcel_uuid16 = 511 ParcelUuid.fromString("0000${uuid16}-0000-1000-8000-00805F9B34FB") 512 advertisingDataBuilder.addServiceSolicitationUuid(parcel_uuid16) 513 } 514 for (uuid32 in dataTypesRequest.getServiceSolicitationUuids32List()) { 515 val parcel_uuid32 = 516 ParcelUuid.fromString("${uuid32}-0000-1000-8000-00805F9B34FB") 517 advertisingDataBuilder.addServiceSolicitationUuid(parcel_uuid32) 518 } 519 for (uuid128 in dataTypesRequest.getServiceSolicitationUuids128List()) { 520 advertisingDataBuilder.addServiceSolicitationUuid( 521 ParcelUuid.fromString(uuid128) 522 ) 523 } 524 525 // Handle service data uuids 526 for ((uuid16, data) in dataTypesRequest.getServiceDataUuid16()) { 527 val parcel_uuid16 = 528 ParcelUuid.fromString("0000${uuid16}-0000-1000-8000-00805F9B34FB") 529 advertisingDataBuilder.addServiceData(parcel_uuid16, data.toByteArray()) 530 } 531 for ((uuid32, data) in dataTypesRequest.getServiceDataUuid32()) { 532 val parcel_uuid32 = 533 ParcelUuid.fromString("${uuid32}-0000-1000-8000-00805F9B34FB") 534 advertisingDataBuilder.addServiceData(parcel_uuid32, data.toByteArray()) 535 } 536 for ((uuid128, data) in dataTypesRequest.getServiceDataUuid128()) { 537 advertisingDataBuilder.addServiceData( 538 ParcelUuid.fromString(uuid128), 539 data.toByteArray() 540 ) 541 } 542 543 advertisingDataBuilder 544 .setIncludeDeviceName( 545 dataTypesRequest.includeCompleteLocalName || 546 dataTypesRequest.includeShortenedLocalName 547 ) 548 .setIncludeTxPowerLevel(dataTypesRequest.includeTxPowerLevel) 549 .addManufacturerData( 550 BluetoothAssignedNumbers.GOOGLE, 551 dataTypesRequest.manufacturerSpecificData.toByteArray() 552 ) 553 val advertisingData = advertisingDataBuilder.build() 554 555 val ownAddressType = 556 when (request.ownAddressType) { 557 OwnAddressType.RESOLVABLE_OR_PUBLIC, 558 OwnAddressType.PUBLIC -> AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC 559 OwnAddressType.RESOLVABLE_OR_RANDOM, 560 OwnAddressType.RANDOM -> AdvertisingSetParameters.ADDRESS_TYPE_RANDOM 561 else -> AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT 562 } 563 564 val advertisingSetParameters = 565 AdvertisingSetParameters.Builder() 566 .setConnectable(request.connectable) 567 .setOwnAddressType(ownAddressType) 568 .setLegacyMode(request.legacy) 569 .setScannable(request.legacy && request.connectable) 570 .build() 571 572 bluetoothAdapter.bluetoothLeAdvertiser.startAdvertisingSet( 573 advertisingSetParameters, 574 advertisingData, 575 null, /* scanResponse */ 576 null, /* periodicParameters */ 577 null, /* periodicData */ 578 callback, 579 ) 580 581 if (request.connectable) { 582 while (true) { 583 Log.d(TAG, "Waiting for incoming connection") 584 val connection = 585 waitIncomingAclConnectedIntent(null, TRANSPORT_LE) 586 .getBluetoothDeviceExtra() 587 .toConnection(TRANSPORT_LE) 588 Log.d(TAG, "Receive connection") 589 trySendBlocking( 590 AdvertiseResponse.newBuilder().setConnection(connection).build() 591 ) 592 } 593 } 594 595 awaitClose { bluetoothAdapter.bluetoothLeAdvertiser.stopAdvertisingSet(callback) } 596 } 597 } 598 } 599 600 // TODO: Handle request parameters scannull601 override fun scan(request: ScanRequest, responseObserver: StreamObserver<ScanningResponse>) { 602 Log.d(TAG, "scan") 603 grpcServerStream(scope, responseObserver) { 604 callbackFlow { 605 val callback = 606 object : ScanCallback() { 607 override fun onScanResult(callbackType: Int, result: ScanResult) { 608 val bluetoothDevice = result.device 609 val scanRecord = result.scanRecord 610 checkNotNull(scanRecord) 611 val scanData = scanRecord.getAdvertisingDataMap() 612 val serviceData = scanRecord.serviceData 613 checkNotNull(serviceData) 614 615 var dataTypesBuilder = 616 DataTypes.newBuilder().setTxPowerLevel(scanRecord.getTxPowerLevel()) 617 618 scanData[ScanRecord.DATA_TYPE_LOCAL_NAME_SHORT]?.let { 619 dataTypesBuilder.setShortenedLocalName(it.decodeToString()) 620 } 621 ?: run { dataTypesBuilder.setIncludeShortenedLocalName(false) } 622 623 scanData[ScanRecord.DATA_TYPE_LOCAL_NAME_COMPLETE]?.let { 624 dataTypesBuilder.setCompleteLocalName(it.decodeToString()) 625 } 626 ?: run { dataTypesBuilder.setIncludeCompleteLocalName(false) } 627 628 scanData[ScanRecord.DATA_TYPE_ADVERTISING_INTERVAL]?.let { 629 dataTypesBuilder.setAdvertisingInterval( 630 ByteArrayOps.getShortAt(it, 0).toInt() 631 ) 632 } 633 634 scanData[ScanRecord.DATA_TYPE_ADVERTISING_INTERVAL_LONG]?.let { 635 dataTypesBuilder.setAdvertisingInterval( 636 ByteArrayOps.getIntAt(it, 0) 637 ) 638 } 639 640 scanData[ScanRecord.DATA_TYPE_APPEARANCE]?.let { 641 dataTypesBuilder.setAppearance( 642 ByteArrayOps.getShortAt(it, 0).toInt() 643 ) 644 } 645 646 scanData[ScanRecord.DATA_TYPE_CLASS_OF_DEVICE]?.let { 647 dataTypesBuilder.setClassOfDevice(ByteArrayOps.getInt24At(it, 0)) 648 } 649 650 scanData[ScanRecord.DATA_TYPE_URI]?.let { 651 dataTypesBuilder.setUri(it.decodeToString()) 652 } 653 654 scanData[ScanRecord.DATA_TYPE_LE_SUPPORTED_FEATURES]?.let { 655 dataTypesBuilder.setLeSupportedFeatures(ByteString.copyFrom(it)) 656 } 657 658 scanData[ScanRecord.DATA_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE]?.let { 659 dataTypesBuilder.setPeripheralConnectionIntervalMin( 660 ByteArrayOps.getShortAt(it, 0).toInt() 661 ) 662 dataTypesBuilder.setPeripheralConnectionIntervalMax( 663 ByteArrayOps.getShortAt(it, 2).toInt() 664 ) 665 } 666 667 for (serviceDataEntry in serviceData) { 668 val parcelUuid = serviceDataEntry.key 669 Log.d(TAG, parcelUuid.uuid.toString().uppercase()) 670 671 // use upper case uuid as the key 672 if (BluetoothUuid.is16BitUuid(parcelUuid)) { 673 val uuid16 = 674 parcelUuid.uuid.toString().substring(4, 8).uppercase() 675 dataTypesBuilder.putServiceDataUuid16( 676 uuid16, 677 ByteString.copyFrom(serviceDataEntry.value) 678 ) 679 } else if (BluetoothUuid.is32BitUuid(parcelUuid)) { 680 val uuid32 = 681 parcelUuid.uuid.toString().substring(0, 8).uppercase() 682 dataTypesBuilder.putServiceDataUuid32( 683 uuid32, 684 ByteString.copyFrom(serviceDataEntry.value) 685 ) 686 } else { 687 val uuid128 = parcelUuid.uuid.toString().uppercase() 688 dataTypesBuilder.putServiceDataUuid128( 689 uuid128, 690 ByteString.copyFrom(serviceDataEntry.value) 691 ) 692 } 693 } 694 695 for (serviceUuid in 696 scanRecord.serviceSolicitationUuids ?: listOf<ParcelUuid>()) { 697 Log.d(TAG, serviceUuid.uuid.toString().uppercase()) 698 if (BluetoothUuid.is16BitUuid(serviceUuid)) { 699 val uuid16 = 700 serviceUuid.uuid.toString().substring(4, 8).uppercase() 701 dataTypesBuilder.addServiceSolicitationUuids16(uuid16) 702 } else if (BluetoothUuid.is32BitUuid(serviceUuid)) { 703 val uuid32 = 704 serviceUuid.uuid.toString().substring(0, 8).uppercase() 705 dataTypesBuilder.addServiceSolicitationUuids32(uuid32) 706 } else { 707 val uuid128 = serviceUuid.uuid.toString().uppercase() 708 dataTypesBuilder.addServiceSolicitationUuids128(uuid128) 709 } 710 } 711 712 for (serviceUuid in scanRecord.serviceUuids ?: listOf<ParcelUuid>()) { 713 Log.d(TAG, serviceUuid.uuid.toString().uppercase()) 714 if (BluetoothUuid.is16BitUuid(serviceUuid)) { 715 val uuid16 = 716 serviceUuid.uuid.toString().substring(4, 8).uppercase() 717 dataTypesBuilder.addIncompleteServiceClassUuids16(uuid16) 718 } else if (BluetoothUuid.is32BitUuid(serviceUuid)) { 719 val uuid32 = 720 serviceUuid.uuid.toString().substring(0, 8).uppercase() 721 dataTypesBuilder.addIncompleteServiceClassUuids32(uuid32) 722 } else { 723 val uuid128 = serviceUuid.uuid.toString().uppercase() 724 dataTypesBuilder.addIncompleteServiceClassUuids128(uuid128) 725 } 726 } 727 728 // Flags DataTypes CSSv10 1.3 Flags 729 val mode: DiscoverabilityMode = 730 when (scanRecord.advertiseFlags and 0b11) { 731 0b01 -> DiscoverabilityMode.DISCOVERABLE_LIMITED 732 0b10 -> DiscoverabilityMode.DISCOVERABLE_GENERAL 733 else -> DiscoverabilityMode.NOT_DISCOVERABLE 734 } 735 dataTypesBuilder.setLeDiscoverabilityMode(mode) 736 var manufacturerData = ByteBuffer.allocate(512) 737 val manufacturerSpecificDatas = scanRecord.getManufacturerSpecificData() 738 for (i in 0..manufacturerSpecificDatas.size() - 1) { 739 val id = manufacturerSpecificDatas.keyAt(i) 740 manufacturerData 741 .put(id.toByte()) 742 .put(id.shr(8).toByte()) 743 .put(manufacturerSpecificDatas.get(id)) 744 } 745 dataTypesBuilder.setManufacturerSpecificData( 746 ByteString.copyFrom( 747 manufacturerData.array(), 748 0, 749 manufacturerData.position() 750 ) 751 ) 752 val primaryPhy = 753 when (result.getPrimaryPhy()) { 754 BluetoothDevice.PHY_LE_1M -> PrimaryPhy.PRIMARY_1M 755 BluetoothDevice.PHY_LE_CODED -> PrimaryPhy.PRIMARY_CODED 756 else -> PrimaryPhy.UNRECOGNIZED 757 } 758 val secondaryPhy = 759 when (result.getSecondaryPhy()) { 760 ScanResult.PHY_UNUSED -> SecondaryPhy.SECONDARY_NONE 761 BluetoothDevice.PHY_LE_1M -> SecondaryPhy.SECONDARY_1M 762 BluetoothDevice.PHY_LE_2M -> SecondaryPhy.SECONDARY_2M 763 BluetoothDevice.PHY_LE_CODED -> SecondaryPhy.SECONDARY_CODED 764 else -> SecondaryPhy.UNRECOGNIZED 765 } 766 var scanningResponseBuilder = 767 ScanningResponse.newBuilder() 768 .setLegacy(result.isLegacy()) 769 .setConnectable(result.isConnectable()) 770 .setTruncated( 771 result.getDataStatus() == ScanResult.DATA_TRUNCATED 772 ) 773 .setSid(result.getAdvertisingSid()) 774 .setPrimaryPhy(primaryPhy) 775 .setSecondaryPhy(secondaryPhy) 776 .setTxPower(result.getTxPower()) 777 .setRssi(result.getRssi()) 778 .setPeriodicAdvertisingInterval( 779 result.getPeriodicAdvertisingInterval().toFloat() 780 ) 781 .setData(dataTypesBuilder.build()) 782 when (bluetoothDevice.addressType) { 783 BluetoothDevice.ADDRESS_TYPE_PUBLIC -> 784 scanningResponseBuilder.setPublic( 785 bluetoothDevice.toByteString() 786 ) 787 BluetoothDevice.ADDRESS_TYPE_RANDOM -> 788 scanningResponseBuilder.setRandom( 789 bluetoothDevice.toByteString() 790 ) 791 else -> 792 Log.w( 793 TAG, 794 "Address type UNKNOWN: ${bluetoothDevice.type} addr: $bluetoothDevice" 795 ) 796 } 797 // TODO: Complete the missing field as needed, all the examples are here 798 trySendBlocking(scanningResponseBuilder.build()) 799 } 800 801 override fun onScanFailed(errorCode: Int) { 802 error("scan failed") 803 } 804 } 805 val scanSettings = ScanSettings.Builder().setLegacy(request.legacy).build() 806 bluetoothAdapter.bluetoothLeScanner.startScan(null, scanSettings, callback) 807 808 awaitClose { bluetoothAdapter.bluetoothLeScanner.stopScan(callback) } 809 } 810 } 811 } 812 inquirynull813 override fun inquiry(request: Empty, responseObserver: StreamObserver<InquiryResponse>) { 814 Log.d(TAG, "Inquiry") 815 grpcServerStream(scope, responseObserver) { 816 launch { 817 try { 818 bluetoothAdapter.startDiscovery() 819 awaitCancellation() 820 } finally { 821 bluetoothAdapter.cancelDiscovery() 822 } 823 } 824 flow 825 .filter { it.action == BluetoothDevice.ACTION_FOUND } 826 .map { 827 val bluetoothDevice = it.getBluetoothDeviceExtra() 828 Log.i(TAG, "Device found: $bluetoothDevice") 829 InquiryResponse.newBuilder().setAddress(bluetoothDevice.toByteString()).build() 830 } 831 } 832 } 833 setDiscoverabilityModenull834 override fun setDiscoverabilityMode( 835 request: SetDiscoverabilityModeRequest, 836 responseObserver: StreamObserver<Empty> 837 ) { 838 Log.d(TAG, "setDiscoverabilityMode") 839 grpcUnary(scope, responseObserver) { 840 discoverability = request.mode!! 841 842 val scanMode = 843 when (discoverability) { 844 DiscoverabilityMode.UNRECOGNIZED -> null 845 DiscoverabilityMode.NOT_DISCOVERABLE -> 846 if (connectability == ConnectabilityMode.CONNECTABLE) { 847 BluetoothAdapter.SCAN_MODE_CONNECTABLE 848 } else { 849 BluetoothAdapter.SCAN_MODE_NONE 850 } 851 DiscoverabilityMode.DISCOVERABLE_LIMITED, 852 DiscoverabilityMode.DISCOVERABLE_GENERAL -> 853 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE 854 } 855 856 if (scanMode != null) { 857 bluetoothAdapter.setScanMode(scanMode) 858 } 859 860 if (discoverability == DiscoverabilityMode.DISCOVERABLE_LIMITED) { 861 bluetoothAdapter.setDiscoverableTimeout( 862 Duration.ofSeconds(120) 863 ) // limited discoverability needs a timeout, 120s is Android default 864 } 865 Empty.getDefaultInstance() 866 } 867 } 868 setConnectabilityModenull869 override fun setConnectabilityMode( 870 request: SetConnectabilityModeRequest, 871 responseObserver: StreamObserver<Empty> 872 ) { 873 grpcUnary(scope, responseObserver) { 874 Log.d(TAG, "setConnectabilityMode") 875 connectability = request.mode!! 876 877 val scanMode = 878 when (connectability) { 879 ConnectabilityMode.UNRECOGNIZED -> null 880 ConnectabilityMode.NOT_CONNECTABLE -> { 881 BluetoothAdapter.SCAN_MODE_NONE 882 } 883 ConnectabilityMode.CONNECTABLE -> { 884 if ( 885 discoverability == DiscoverabilityMode.DISCOVERABLE_LIMITED || 886 discoverability == DiscoverabilityMode.DISCOVERABLE_GENERAL 887 ) { 888 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE 889 } else { 890 BluetoothAdapter.SCAN_MODE_CONNECTABLE 891 } 892 } 893 } 894 if (scanMode != null) { 895 bluetoothAdapter.setScanMode(scanMode) 896 } 897 Empty.getDefaultInstance() 898 } 899 } 900 } 901