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