Warning, /network/kdeconnect-android/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.kt is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Saikrishna Arcot <saiarcot895@gmail.com> 0003 * SPDX-FileCopyrightText: 2024 Rob Emery <git@mintsoft.net> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 package org.kde.kdeconnect.Backends.BluetoothBackend 0008 0009 import android.annotation.SuppressLint 0010 import android.bluetooth.BluetoothAdapter 0011 import android.bluetooth.BluetoothDevice 0012 import android.bluetooth.BluetoothServerSocket 0013 import android.bluetooth.BluetoothSocket 0014 import android.content.BroadcastReceiver 0015 import android.content.Context 0016 import android.content.Intent 0017 import android.content.IntentFilter 0018 import android.net.Network 0019 import android.preference.PreferenceManager 0020 import android.util.Base64 0021 import android.util.Log 0022 import org.apache.commons.io.IOUtils 0023 import org.kde.kdeconnect.Backends.BaseLinkProvider 0024 import org.kde.kdeconnect.Device 0025 import org.kde.kdeconnect.DeviceInfo.Companion.fromIdentityPacketAndCert 0026 import org.kde.kdeconnect.Helpers.DeviceHelper 0027 import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper 0028 import org.kde.kdeconnect.Helpers.ThreadHelper.execute 0029 import org.kde.kdeconnect.NetworkPacket 0030 import org.kde.kdeconnect.UserInterface.SettingsFragment 0031 import java.io.IOException 0032 import java.io.InputStreamReader 0033 import java.io.Reader 0034 import java.security.cert.CertificateException 0035 import java.util.UUID 0036 import kotlin.text.Charsets.UTF_8 0037 0038 class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() { 0039 private val visibleDevices: MutableMap<String, BluetoothLink> = HashMap() 0040 private val sockets: MutableMap<BluetoothDevice?, BluetoothSocket> = HashMap() 0041 private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter() 0042 private var serverRunnable: ServerRunnable? = null 0043 private var clientRunnable: ClientRunnable? = null 0044 0045 @Throws(CertificateException::class) 0046 private fun addLink(identityPacket: NetworkPacket, link: BluetoothLink) { 0047 val deviceId = identityPacket.getString("deviceId") 0048 Log.i("BluetoothLinkProvider", "addLink to $deviceId") 0049 val oldLink = visibleDevices[deviceId] 0050 if (oldLink == link) { 0051 Log.e("BluetoothLinkProvider", "oldLink == link. This should not happen!") 0052 return 0053 } 0054 synchronized(visibleDevices) { visibleDevices.put(deviceId, link) } 0055 onConnectionReceived(link) 0056 link.startListening() 0057 link.packetReceived(identityPacket) 0058 if (oldLink != null) { 0059 Log.i("BluetoothLinkProvider", "Removing old connection to same device") 0060 oldLink.disconnect() 0061 } 0062 } 0063 0064 init { 0065 if (bluetoothAdapter == null) { 0066 Log.e("BluetoothLinkProvider", "No bluetooth adapter found.") 0067 } 0068 } 0069 0070 override fun onStart() { 0071 val preferences = PreferenceManager.getDefaultSharedPreferences(context) 0072 if (!preferences.getBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)) { 0073 return 0074 } 0075 if (bluetoothAdapter == null || bluetoothAdapter.isEnabled == false) { 0076 return 0077 } 0078 Log.i("BluetoothLinkProvider", "onStart called") 0079 0080 //This handles the case when I'm the existing device in the network and receive a hello package 0081 clientRunnable = ClientRunnable() 0082 execute(clientRunnable!!) 0083 0084 // I'm on a new network, let's be polite and introduce myself 0085 serverRunnable = ServerRunnable() 0086 execute(serverRunnable!!) 0087 } 0088 0089 override fun onNetworkChange(network: Network?) { 0090 Log.i("BluetoothLinkProvider", "onNetworkChange called") 0091 onStop() 0092 onStart() 0093 } 0094 0095 override fun onStop() { 0096 if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) { 0097 return 0098 } 0099 Log.i("BluetoothLinkProvider", "onStop called") 0100 clientRunnable!!.stopProcessing() 0101 serverRunnable!!.stopProcessing() 0102 } 0103 0104 override fun getName(): String { 0105 return "BluetoothLinkProvider" 0106 } 0107 0108 override fun getPriority(): Int { 0109 return 10 0110 } 0111 0112 fun disconnectedLink(link: BluetoothLink, remoteAddress: BluetoothDevice?) { 0113 Log.i("BluetoothLinkProvider", "disconnectedLink called") 0114 synchronized(sockets) { sockets.remove(remoteAddress) } 0115 synchronized(visibleDevices) { visibleDevices.remove(link.deviceId) } 0116 onConnectionLost(link) 0117 } 0118 0119 private inner class ServerRunnable : Runnable { 0120 private var continueProcessing = true 0121 private var serverSocket: BluetoothServerSocket? = null 0122 fun stopProcessing() { 0123 continueProcessing = false 0124 try { 0125 IOUtils.close(serverSocket) 0126 } catch (e: IOException) { 0127 Log.e("KDEConnect", "Exception", e) 0128 } 0129 } 0130 0131 override fun run() { 0132 serverSocket = try { 0133 bluetoothAdapter!!.listenUsingRfcommWithServiceRecord("KDE Connect", SERVICE_UUID) 0134 } catch (e: IOException) { 0135 Log.e("KDEConnect", "Exception", e) 0136 return 0137 } catch (e: SecurityException) { 0138 Log.e("KDEConnect", "Security Exception for CONNECT", e) 0139 return 0140 } 0141 try { 0142 while (continueProcessing) { 0143 val socket = serverSocket!!.accept() 0144 connect(socket) 0145 } 0146 } catch (e: Exception) { 0147 Log.d("BTLinkProvider/Server", "Bluetooth Server error", e) 0148 } 0149 } 0150 0151 @Throws(Exception::class) 0152 private fun connect(socket: BluetoothSocket) { 0153 synchronized(sockets) { 0154 if (sockets.containsKey(socket.remoteDevice)) { 0155 Log.i("BTLinkProvider/Server", "Received duplicate connection from " + socket.remoteDevice.address) 0156 socket.close() 0157 return 0158 } else { 0159 sockets.put(socket.remoteDevice, socket) 0160 } 0161 } 0162 Log.i("BTLinkProvider/Server", "Received connection from " + socket.remoteDevice.address) 0163 0164 //Delay to let bluetooth initialize stuff correctly 0165 try { 0166 Thread.sleep(500) 0167 } catch (e: Exception) { 0168 synchronized(sockets) { sockets.remove(socket.remoteDevice) } 0169 throw e 0170 } 0171 try { 0172 ConnectionMultiplexer(socket).use { connection -> 0173 val outputStream = connection.defaultOutputStream 0174 val inputStream = connection.defaultInputStream 0175 val myDeviceInfo = DeviceHelper.getDeviceInfo(context) 0176 val np = myDeviceInfo.toIdentityPacket() 0177 np["certificate"] = Base64.encodeToString(SslHelper.certificate.encoded, 0) 0178 val message = np.serialize().toByteArray(UTF_8) 0179 outputStream.write(message) 0180 outputStream.flush() 0181 Log.i("BTLinkProvider/Server", "Sent identity packet") 0182 0183 // Listen for the response 0184 val sb = StringBuilder() 0185 val reader: Reader = InputStreamReader(inputStream, UTF_8) 0186 var charsRead = 0 0187 val buf = CharArray(512) 0188 while (sb.lastIndexOf("\n") == -1 && reader.read(buf).also { charsRead = it } != -1) { 0189 sb.append(buf, 0, charsRead) 0190 } 0191 val response = sb.toString() 0192 val identityPacket = NetworkPacket.unserialize(response) 0193 if (identityPacket.type != NetworkPacket.PACKET_TYPE_IDENTITY) { 0194 Log.e("BTLinkProvider/Server", "2 Expecting an identity packet") 0195 return 0196 } 0197 Log.i("BTLinkProvider/Server", "Received identity packet") 0198 val pemEncodedCertificateString = identityPacket.getString("certificate") 0199 val base64CertificateString = pemEncodedCertificateString 0200 .replace("-----BEGIN CERTIFICATE-----\n", "") 0201 .replace("-----END CERTIFICATE-----\n", "") 0202 val pemEncodedCertificateBytes = Base64.decode(base64CertificateString, 0) 0203 val certificate = SslHelper.parseCertificate(pemEncodedCertificateBytes) 0204 val deviceInfo = fromIdentityPacketAndCert(identityPacket, certificate) 0205 Log.i("BTLinkProvider/Server", "About to create link") 0206 val link = BluetoothLink(context, connection, 0207 inputStream, outputStream, socket.remoteDevice, 0208 deviceInfo, this@BluetoothLinkProvider) 0209 Log.i("BTLinkProvider/Server", "About to addLink") 0210 addLink(identityPacket, link) 0211 Log.i("BTLinkProvider/Server", "Link Added") 0212 } 0213 } catch (e: Exception) { 0214 synchronized(sockets) { 0215 sockets.remove(socket.remoteDevice) 0216 Log.i("BTLinkProvider/Server", "Exception thrown, removing socket", e) 0217 } 0218 throw e 0219 } 0220 } 0221 } 0222 0223 object ClientRunnableSingleton { 0224 val connectionThreads: MutableMap<BluetoothDevice?, Thread> = HashMap() 0225 } 0226 0227 private inner class ClientRunnable : BroadcastReceiver(), Runnable { 0228 private var continueProcessing = true 0229 fun stopProcessing() { 0230 continueProcessing = false 0231 } 0232 0233 override fun run() { 0234 Log.i("ClientRunnable", "run called") 0235 val filter = IntentFilter(BluetoothDevice.ACTION_UUID) 0236 context.registerReceiver(this, filter) 0237 Log.i("ClientRunnable", "receiver registered") 0238 if (continueProcessing) { 0239 Log.i("ClientRunnable", "before connectToDevices") 0240 discoverDeviceServices() 0241 Log.i("ClientRunnable", "after connectToDevices") 0242 try { 0243 Thread.sleep(15000) 0244 } catch (ignored: InterruptedException) { 0245 } 0246 } 0247 Log.i("ClientRunnable", "unregisteringReceiver") 0248 context.unregisterReceiver(this) 0249 } 0250 0251 /** 0252 * Tell Android to use ServiceDiscoveryProtocol to update the 0253 * list of available UUIDs associated with Bluetooth devices 0254 * that are bluetooth-paired-but-not-yet-kde-paired 0255 */ 0256 @SuppressLint("MissingPermission") 0257 private fun discoverDeviceServices() { 0258 Log.i("ClientRunnable", "connectToDevices called") 0259 val pairedDevices = bluetoothAdapter!!.bondedDevices 0260 if (pairedDevices == null) { 0261 Log.i("BluetoothLinkProvider", "Paired Devices is NULL") 0262 return 0263 } 0264 Log.i("BluetoothLinkProvider", "Bluetooth adapter paired devices: " + pairedDevices.size) 0265 0266 // Loop through Bluetooth paired devices 0267 for (device in pairedDevices) { 0268 // If a socket exists for this, then it has been paired in KDE 0269 if (sockets.containsKey(device)) { 0270 continue 0271 } 0272 Log.i("ClientRunnable", "Calling fetchUuidsWithSdp for device: $device") 0273 device.fetchUuidsWithSdp() 0274 val deviceUuids = device.uuids 0275 if (deviceUuids != null) { 0276 for (thisUuid in deviceUuids) { 0277 Log.i("ClientRunnable", "device $device uuid: $thisUuid") 0278 } 0279 } 0280 } 0281 } 0282 0283 override fun onReceive(context: Context, intent: Intent) { 0284 val action = intent.action 0285 if (BluetoothDevice.ACTION_UUID == action) { 0286 Log.i("BluetoothLinkProvider", "Action matches") 0287 val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) 0288 val activeUuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID) 0289 if (sockets.containsKey(device)) { 0290 Log.i("BluetoothLinkProvider", "sockets contains device") 0291 return 0292 } 0293 if (activeUuids == null) { 0294 Log.i("BluetoothLinkProvider", "activeUuids is null") 0295 return 0296 } 0297 for (uuid in activeUuids) { 0298 if (uuid.toString() == SERVICE_UUID.toString() || uuid.toString() == BYTE_REVERSED_SERVICE_UUID.toString()) { 0299 Log.i("BluetoothLinkProvider", "calling connectToDevice for device: " + device!!.address) 0300 connectToDevice(device) 0301 return 0302 } 0303 } 0304 } 0305 } 0306 0307 private fun connectToDevice(device: BluetoothDevice?) { 0308 synchronized(ClientRunnableSingleton.connectionThreads) { 0309 if (!ClientRunnableSingleton.connectionThreads.containsKey(device) || !ClientRunnableSingleton.connectionThreads[device]!!.isAlive) { 0310 val connectionThread = Thread(ClientConnect(device)) 0311 connectionThread.start() 0312 ClientRunnableSingleton.connectionThreads[device] = connectionThread 0313 } 0314 } 0315 } 0316 } 0317 0318 private inner class ClientConnect(private val device: BluetoothDevice?) : Runnable { 0319 override fun run() { 0320 connectToDevice() 0321 } 0322 0323 private fun connectToDevice() { 0324 val socket: BluetoothSocket 0325 try { 0326 Log.i("BTLinkProvider/Client", "Cancelling Discovery") 0327 bluetoothAdapter!!.cancelDiscovery() 0328 Log.i("BTLinkProvider/Client", "Creating RFCommSocket to Service Record") 0329 socket = device!!.createRfcommSocketToServiceRecord(SERVICE_UUID) 0330 Log.i("BTLinkProvider/Client", "Connecting to ServiceRecord Socket") 0331 socket.connect() 0332 synchronized(sockets) { sockets.put(device, socket) } 0333 } catch (e: IOException) { 0334 Log.e("BTLinkProvider/Client", "Could not connect to KDE Connect service on " + device!!.address, e) 0335 return 0336 } catch (e: SecurityException) { 0337 Log.e("BTLinkProvider/Client", "Security Exception connecting to " + device!!.address, e) 0338 return 0339 } 0340 Log.i("BTLinkProvider/Client", "Connected to " + device.address) 0341 try { 0342 //Delay to let bluetooth initialize stuff correctly 0343 Thread.sleep(500) 0344 val connection = ConnectionMultiplexer(socket) 0345 val outputStream = connection.defaultOutputStream 0346 val inputStream = connection.defaultInputStream 0347 Log.i("BTLinkProvider/Client", "Device: " + device.address + " Before inputStream.read()") 0348 var character = 0 0349 val sb = StringBuilder() 0350 while (sb.lastIndexOf("\n") == -1 && inputStream.read().also { character = it } != -1) { 0351 sb.append(character.toChar()) 0352 } 0353 Log.i("BTLinkProvider/Client", "Device: " + device.address + " Before sb.toString()") 0354 val message = sb.toString() 0355 Log.i("BTLinkProvider/Client", "Device: " + device.address + " Before unserialize (message: '" + message + "')") 0356 val identityPacket = NetworkPacket.unserialize(message) 0357 Log.i("BTLinkProvider/Client", "Device: " + device.address + " After unserialize") 0358 if (identityPacket.type != NetworkPacket.PACKET_TYPE_IDENTITY) { 0359 Log.e("BTLinkProvider/Client", "1 Expecting an identity packet") 0360 socket.close() 0361 return 0362 } 0363 Log.i("BTLinkProvider/Client", "Received identity packet") 0364 val myId = DeviceHelper.getDeviceId(context) 0365 if (identityPacket.getString("deviceId") == myId) { 0366 // Probably won't happen, but just to be safe 0367 connection.close() 0368 return 0369 } 0370 if (visibleDevices.containsKey(identityPacket.getString("deviceId"))) { 0371 return 0372 } 0373 Log.i("BTLinkProvider/Client", "identity packet received, creating link") 0374 val pemEncodedCertificateString = identityPacket.getString("certificate") 0375 val base64CertificateString = pemEncodedCertificateString 0376 .replace("-----BEGIN CERTIFICATE-----\n", "") 0377 .replace("-----END CERTIFICATE-----\n", "") 0378 val pemEncodedCertificateBytes = Base64.decode(base64CertificateString, 0) 0379 val certificate = SslHelper.parseCertificate(pemEncodedCertificateBytes) 0380 val deviceInfo = fromIdentityPacketAndCert(identityPacket, certificate) 0381 val link = BluetoothLink(context, connection, inputStream, outputStream, 0382 socket.remoteDevice, deviceInfo, this@BluetoothLinkProvider) 0383 val myDeviceInfo = DeviceHelper.getDeviceInfo(context) 0384 val np2 = myDeviceInfo.toIdentityPacket() 0385 np2["certificate"] = Base64.encodeToString(SslHelper.certificate.encoded, 0) 0386 Log.i("BTLinkProvider/Client", "about to send packet np2") 0387 link.sendPacket(np2, object : Device.SendPacketStatusCallback() { 0388 override fun onSuccess() { 0389 try { 0390 addLink(identityPacket, link) 0391 } catch (e: CertificateException) { 0392 e.printStackTrace() 0393 } 0394 } 0395 0396 override fun onFailure(e: Throwable) {} 0397 }, true) 0398 } catch (e: Exception) { 0399 Log.e("BTLinkProvider/Client", "Connection lost/disconnected on " + device.address, e) 0400 synchronized(sockets) { sockets.remove(device, socket) } 0401 } 0402 } 0403 } 0404 0405 companion object { 0406 private val SERVICE_UUID = UUID.fromString("185f3df4-3268-4e3f-9fca-d4d5059915bd") 0407 private val BYTE_REVERSED_SERVICE_UUID = UUID(java.lang.Long.reverseBytes(SERVICE_UUID.leastSignificantBits), java.lang.Long.reverseBytes(SERVICE_UUID.mostSignificantBits)) 0408 } 0409 }