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