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 }