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