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.BluetoothServerSocket 21 import android.bluetooth.BluetoothSocket 22 import android.bluetooth.le.AdvertiseCallback 23 import android.bluetooth.le.AdvertiseData 24 import android.bluetooth.le.AdvertiseSettings 25 import android.os.Handler 26 import android.os.Looper 27 import android.os.ParcelUuid 28 import android.util.Log 29 import com.android.devicediagnostics.Protos.BluetoothPacket 30 import com.android.devicediagnostics.Protos.PacketCommand 31 import com.android.devicediagnostics.Protos.TrustedDeviceInfo 32 import com.android.devicediagnostics.runInBackground 33 import java.util.UUID 34 35 val SERVICE_UUID: UUID = UUID.fromString("0000b81d-0000-1000-8000-00805f9b34fb") 36 private const val TAG = "BluetoothServer" 37 const val CHUNK = 4096 38 39 class BluetoothServer { 40 private val adapter 41 get() = BluetoothAdapter.getDefaultAdapter() 42 43 private var socket: BluetoothSocket? = null 44 private var starter: ServerStarter? = null 45 46 interface StartListener { onServerAvailablenull47 fun onServerAvailable(connectionData: BluetoothConnectionData) 48 49 fun onServerStarted() 50 51 fun onServerError() 52 } 53 54 interface ReadListener { 55 fun onBluetoothReadPacket(packet: BluetoothPacket) 56 57 fun onBluetoothReadError(e: Exception) 58 } 59 startnull60 fun start(trustedDeviceInfo: TrustedDeviceInfo, listener: StartListener) { 61 if (starter != null) { 62 starter!!.stop() 63 } 64 65 val server = this 66 val callbacks = 67 object : ServerStarter.Listener { 68 override fun onServerAdvertiseStarted(connectionData: BluetoothConnectionData) { 69 listener.onServerAvailable(connectionData) 70 } 71 72 override fun onServerConnected(socket: BluetoothSocket) { 73 server.socket = socket 74 listener.onServerStarted() 75 starter = null 76 } 77 78 override fun onServerAdvertiseError() { 79 listener.onServerError() 80 } 81 } 82 starter = ServerStarter(adapter, trustedDeviceInfo, callbacks) 83 starter!!.start() 84 } 85 startReadnull86 fun startRead(listener: ReadListener) { 87 val handler = Handler(Looper.myLooper()!!) 88 val localSocket = socket 89 runInBackground { 90 try { 91 if (localSocket == null) { 92 throw Exception("Socket is closed") 93 } 94 val packet = readBluetoothPacket(localSocket) 95 handler.post() { listener.onBluetoothReadPacket(packet) } 96 } catch (e: Exception) { 97 handler.post() { listener.onBluetoothReadError(e) } 98 return@runInBackground 99 } 100 101 try { 102 val ack = BluetoothPacket.newBuilder().setCommand(PacketCommand.COMMAND_ACK) 103 writeBluetoothPacket(socket!!, ack) 104 } catch (e: Exception) { 105 Log.e(TAG, "Error sending report acknowledgement: $e") 106 return@runInBackground 107 } 108 109 // Wait for the client to hang up before resetting. 110 try { 111 readBluetoothPacketOrEof(localSocket) 112 } catch (_: Exception) {} finally { 113 reset() 114 } 115 } 116 } 117 resetnull118 fun reset() { 119 starter?.stop() 120 starter = null 121 socket?.close() 122 socket = null 123 } 124 125 val bluetoothEnabled 126 get() = adapter.isEnabled 127 } 128 129 class ServerStarter( 130 private val adapter: BluetoothAdapter, 131 private val trustedDeviceInfo: TrustedDeviceInfo, 132 private val listener: ServerStarter.Listener 133 ) { 134 private val handler = Handler(Looper.myLooper()!!) 135 private val advertiseSettings = 136 AdvertiseSettings.Builder() 137 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) 138 .setTimeout(0) 139 .build() 140 private val advertiseData = 141 AdvertiseData.Builder() 142 .addServiceUuid(ParcelUuid(SERVICE_UUID)) 143 .setIncludeDeviceName(true) 144 .build() 145 private val advertiser = adapter.bluetoothLeAdvertiser 146 private var advertiseCallback: AdvertiseCallback? = null 147 private var active: Boolean = false 148 149 // Access to this should be protected by synchronized(this). 150 private var serverSocket: BluetoothServerSocket? = null 151 152 interface Listener { onServerAdvertiseStartednull153 fun onServerAdvertiseStarted(connectionData: BluetoothConnectionData) 154 155 fun onServerConnected(socket: BluetoothSocket) 156 157 fun onServerAdvertiseError() 158 } 159 160 fun start() { 161 active = true 162 163 advertiseCallback = 164 object : AdvertiseCallback() { 165 override fun onStartFailure(errorCode: Int) { 166 postServerError(Exception("Advertisement failed, error code $errorCode")) 167 } 168 169 override fun onStartSuccess(settings: AdvertiseSettings?) { 170 Log.d(TAG, "Advertisement started") 171 } 172 } 173 advertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback!!) 174 Log.d(TAG, "startAdvertisement: with advertiser $advertiser") 175 176 serverSocket = adapter.listenUsingInsecureL2capChannel() 177 Log.d(TAG, "Server started with psm ${serverSocket!!.psm}") 178 179 val connectionData = 180 BluetoothConnectionData(serverSocket!!.psm, trustedDeviceInfo.challenge.toByteArray()) 181 listener.onServerAdvertiseStarted(connectionData) 182 183 val packet = BluetoothPacket.newBuilder().setTrustedDeviceInfo(trustedDeviceInfo) 184 185 // Thread since this waits for connections and otherwise will block Ux 186 runInBackground() { 187 synchronized(this) { 188 if (serverSocket == null) { 189 return@runInBackground 190 } 191 192 try { 193 val socket = serverSocket!!.accept() 194 stopAdvertising() 195 196 writeBluetoothPacket(socket, packet) 197 Log.d(TAG, "wrote start message") 198 199 postServerStarted(socket) 200 } catch (e: Exception) { 201 postServerError(e) 202 } 203 } 204 } 205 } 206 postServerStartednull207 private fun postServerStarted(socket: BluetoothSocket) { 208 handler.post() { 209 if (active) { 210 listener.onServerConnected(socket) 211 active = false 212 } else { 213 socket.close() 214 } 215 } 216 } 217 postServerErrornull218 private fun postServerError(e: Exception) { 219 handler.post() { 220 if (active) { 221 listener.onServerAdvertiseError() 222 stopAdvertising() 223 active = false 224 } 225 } 226 } 227 stopAdvertisingnull228 private fun stopAdvertising() { 229 serverSocket?.close() 230 synchronized(this) { 231 if (advertiseCallback != null) { 232 advertiser.stopAdvertising(advertiseCallback!!) 233 advertiseCallback = null 234 } 235 if (serverSocket != null) { 236 serverSocket?.close() 237 serverSocket = null 238 } 239 } 240 } 241 stopnull242 fun stop() { 243 stopAdvertising() 244 active = false 245 } 246 } 247