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