1 /* 2 * Copyright 2024 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 * https://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.devicediagnostics.bluetooth 18 19 import android.bluetooth.BluetoothAdapter 20 import android.bluetooth.BluetoothDevice 21 import android.bluetooth.BluetoothSocket 22 import android.bluetooth.le.BluetoothLeScanner 23 import android.bluetooth.le.ScanCallback 24 import android.bluetooth.le.ScanFilter 25 import android.bluetooth.le.ScanResult 26 import android.bluetooth.le.ScanSettings 27 import android.os.ParcelUuid 28 import android.util.Log 29 import com.android.devicediagnostics.Protos.BluetoothPacket 30 import com.android.devicediagnostics.Protos.DeviceReport 31 import com.android.devicediagnostics.Protos.PacketCommand 32 import com.android.devicediagnostics.Protos.TrustedDeviceInfo 33 import kotlinx.coroutines.sync.Mutex 34 35 private const val TAG = "BluetoothClient" 36 37 abstract class BluetoothClient { 38 interface ScanListener { onBluetoothConnectednull39 fun onBluetoothConnected(trustedDeviceInfo: TrustedDeviceInfo) 40 41 fun onBluetoothConnectionFailed() 42 } 43 44 var connectionData: BluetoothConnectionData? = null 45 46 abstract fun start(listener: ScanListener) 47 48 abstract fun sendReport(report: DeviceReport) 49 50 abstract fun reset() 51 } 52 53 class BluetoothClientImpl : BluetoothClient() { 54 override fun start(listener: ScanListener) { 55 synchronized(lock) { startImpl(listener) } 56 } 57 58 private fun startImpl(listener: ScanListener) { 59 this.listener = listener 60 scanner = adapter.bluetoothLeScanner 61 scanCallback = MyScanCallback() 62 63 val filters = listOf(ScanFilter.Builder().setServiceUuid(ParcelUuid(SERVICE_UUID)).build()) 64 val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER).build() 65 scanner!!.startScan(filters, settings, scanCallback!!) 66 Log.d(TAG, "Started scan $scanner") 67 } 68 69 private fun readTrustedDeviceInfo(): TrustedDeviceInfo { 70 val message = readBluetoothPacket(socket!!) 71 if (!message.hasTrustedDeviceInfo()) { 72 throw Exception("Expected TrustedDeviceInfo message") 73 } 74 return message.trustedDeviceInfo 75 } 76 77 private var socket: BluetoothSocket? = null 78 private var listener: ScanListener? = null 79 private val adapter = BluetoothAdapter.getDefaultAdapter() 80 private var scanner: BluetoothLeScanner? = null 81 private var scanCallback: ScanCallback? = null 82 private var lock = Mutex() 83 84 inner class MyScanCallback : ScanCallback() { 85 override fun onBatchScanResults(results: List<ScanResult>) { 86 for (item in results) { 87 onScanResult(0, item) 88 } 89 } 90 91 override fun onScanResult(callbackType: Int, result: ScanResult) { 92 if (result.device == null) { 93 return 94 } 95 96 synchronized(lock) { 97 if (scanCallback != this) { 98 return 99 } 100 connect(result.device) 101 } 102 } 103 104 override fun onScanFailed(errorCode: Int) { 105 synchronized(lock) { 106 if (scanCallback != this) { 107 return 108 } 109 Log.d(TAG, "Scan failed with error: $errorCode") 110 listener?.onBluetoothConnectionFailed() 111 } 112 } 113 } 114 115 override fun reset() { 116 synchronized(lock) { resetImpl() } 117 } 118 119 private fun resetImpl() { 120 connectionData = null 121 try { 122 stopScanning() 123 socket?.close() 124 } catch (_: Exception) {} 125 socket = null 126 listener = null 127 scanner = null 128 } 129 130 private fun stopScanning() { 131 Log.d(TAG, "stopped scan") 132 if (scanCallback != null) { 133 scanner?.stopScan(scanCallback!!) 134 scanCallback = null 135 } 136 adapter.cancelDiscovery() 137 listener = null 138 } 139 140 override fun sendReport(report: DeviceReport) { 141 synchronized(lock) { 142 val packet = BluetoothPacket.newBuilder().setDeviceReport(report) 143 writeBluetoothPacket(socket!!, packet) 144 val reply = readBluetoothPacket(socket!!) 145 if (reply.command != PacketCommand.COMMAND_ACK) { 146 throw Exception("Expected acknowledgement packet") 147 } 148 resetImpl() 149 } 150 } 151 152 private fun connect(device: BluetoothDevice) { 153 var trustedDeviceInfo: TrustedDeviceInfo? = null 154 try { 155 Log.d(TAG, "Found device ${device.name}") 156 socket = device.createInsecureL2capChannel(connectionData!!.psm) 157 socket!!.connect() 158 159 trustedDeviceInfo = readTrustedDeviceInfo() 160 val challenge = trustedDeviceInfo.challenge.toByteArray() 161 if (!challenge.contentEquals(connectionData!!.challenge)) { 162 throw Exception("Trusted device has unexpected challenge") 163 } 164 Log.i(TAG, "Bluetooth connection succeeded") 165 } catch (e: Exception) { 166 Log.e(TAG, "Bluetooth connection error: $e") 167 return 168 } 169 listener?.onBluetoothConnected(trustedDeviceInfo) 170 stopScanning() 171 } 172 } 173