2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 18:07:55 +00:00

169 lines
6.2 KiB
Kotlin
Raw Normal View History

/*
* SPDX-FileCopyrightText: 2016 Saikrishna Arcot <saiarcot895@gmail.com>
* SPDX-FileCopyrightText: 2024 Rob Emery <git@mintsoft.net>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.Backends.BluetoothBackend
import android.bluetooth.BluetoothDevice
import android.content.Context
import android.util.Log
import androidx.annotation.WorkerThread
import org.json.JSONException
import org.json.JSONObject
import org.kde.kdeconnect.Backends.BaseLink
import org.kde.kdeconnect.Device
import org.kde.kdeconnect.DeviceInfo
import org.kde.kdeconnect.NetworkPacket
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.io.OutputStream
import java.io.Reader
import java.util.UUID
import kotlin.text.Charsets.UTF_8
class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input: InputStream, output: OutputStream, remoteAddress: BluetoothDevice, deviceInfo: DeviceInfo, linkProvider: BluetoothLinkProvider) : BaseLink(context!!, linkProvider) {
private val connection: ConnectionMultiplexer?
private val input: InputStream
private val output: OutputStream
private val remoteAddress: BluetoothDevice
private val linkProvider: BluetoothLinkProvider
private val deviceInfo: DeviceInfo
private var continueAccepting = true
private val receivingThread = Thread(object : Runnable {
override fun run() {
val sb = StringBuilder()
try {
val reader: Reader = InputStreamReader(input, UTF_8)
val buf = CharArray(512)
while (continueAccepting) {
while (sb.indexOf("\n") == -1 && continueAccepting) {
var charsRead: Int
if (reader.read(buf).also { charsRead = it } > 0) {
sb.append(buf, 0, charsRead)
}
if (charsRead < 0) {
disconnect()
return
}
}
if (!continueAccepting) break
val endIndex = sb.indexOf("\n")
if (endIndex != -1) {
val message = sb.substring(0, endIndex + 1)
sb.delete(0, endIndex + 1)
processMessage(message)
}
}
} catch (e: IOException) {
Log.e("BluetoothLink/receiving", "Connection to " + remoteAddress.address + " likely broken.", e)
disconnect()
}
}
private fun processMessage(message: String) {
val np: NetworkPacket
np = try {
NetworkPacket.unserialize(message)
} catch (e: JSONException) {
Log.e("BluetoothLink/receiving", "Unable to parse message.", e)
return
}
if (np.hasPayloadTransferInfo()) {
try {
val transferUuid = UUID.fromString(np.payloadTransferInfo.getString("uuid"))
val payloadInputStream = connection.getChannelInputStream(transferUuid)
np.payload = NetworkPacket.Payload(payloadInputStream, np.payloadSize)
} catch (e: Exception) {
Log.e("BluetoothLink/receiving", "Unable to get payload", e)
}
}
packetReceived(np)
}
})
init {
this.connection = connection
this.input = input
this.output = output
this.deviceInfo = deviceInfo
this.remoteAddress = remoteAddress
this.linkProvider = linkProvider
}
fun startListening() {
receivingThread.start()
}
override fun getName(): String {
return "BluetoothLink"
}
override fun getDeviceInfo(): DeviceInfo {
return deviceInfo
}
override fun disconnect() {
if (connection == null) {
return
}
continueAccepting = false
try {
connection.close()
} catch (ignored: IOException) {
}
linkProvider.disconnectedLink(this, remoteAddress)
}
@Throws(JSONException::class, IOException::class)
private fun sendMessage(np: NetworkPacket) {
val message = np.serialize().toByteArray(UTF_8)
output.write(message)
}
@WorkerThread
@Throws(IOException::class)
override fun sendPacket(np: NetworkPacket, callback: Device.SendPacketStatusCallback, sendPayloadFromSameThread: Boolean): Boolean {
// sendPayloadFromSameThread is ignored, we always send from the same thread!
return try {
var transferUuid: UUID? = null
if (np.hasPayload()) {
transferUuid = connection!!.newChannel()
val payloadTransferInfo = JSONObject()
payloadTransferInfo.put("uuid", transferUuid.toString())
np.payloadTransferInfo = payloadTransferInfo
}
sendMessage(np)
if (transferUuid != null) {
try {
connection!!.getChannelOutputStream(transferUuid).use { payloadStream ->
val BUFFER_LENGTH = 1024
val buffer = ByteArray(BUFFER_LENGTH)
var bytesRead: Int
var progress: Long = 0
val stream = np.payload!!.inputStream!!
while (stream.read(buffer).also { bytesRead = it } != -1) {
progress += bytesRead.toLong()
payloadStream.write(buffer, 0, bytesRead)
if (np.payloadSize > 0) {
callback.onPayloadProgressChanged((100 * progress / np.payloadSize).toInt())
}
}
payloadStream.flush()
}
} catch (e: Exception) {
callback.onFailure(e)
return false
}
}
callback.onSuccess()
true
} catch (e: Exception) {
callback.onFailure(e)
false
}
}
}