mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
169 lines
6.2 KiB
Kotlin
169 lines
6.2 KiB
Kotlin
/*
|
|
* 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
|
|
}
|
|
}
|
|
} |