mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
RadioInterfaceService is no longer an Android Service
Removes the AIDL and associated support for the `IRadioInterfaceService`. This should give some performance benefit since radio data no longer has to round-trip through the platform.
This commit is contained in:
parent
a44758e28d
commit
9687fb7370
11 changed files with 134 additions and 221 deletions
|
|
@ -80,15 +80,19 @@ A variable keepAllPackets, if set to true will suppress this behavior and instea
|
|||
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
|
||||
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
class BluetoothInterface(val service: RadioInterfaceService, val address: String) : IRadioInterface,
|
||||
class BluetoothInterface(
|
||||
val context: Context,
|
||||
val service: RadioInterfaceService,
|
||||
val address: String) : IRadioInterface,
|
||||
Logging {
|
||||
|
||||
companion object : Logging, InterfaceFactory('x') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||
rest: String
|
||||
): IRadioInterface = BluetoothInterface(service, rest)
|
||||
): IRadioInterface = BluetoothInterface(context, service, rest)
|
||||
|
||||
init {
|
||||
registerFactory()
|
||||
|
|
@ -226,12 +230,12 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
|
|||
init {
|
||||
// Note: this call does no comms, it just creates the device object (even if the
|
||||
// device is off/not connected)
|
||||
val device = getBluetoothAdapter(service)?.getRemoteDevice(address)
|
||||
val device = getBluetoothAdapter(context)?.getRemoteDevice(address)
|
||||
if (device != null) {
|
||||
info("Creating radio interface service. device=${address.anonymize}")
|
||||
|
||||
// Note this constructor also does no comm
|
||||
val s = SafeBluetooth(service, device)
|
||||
val s = SafeBluetooth(context, device)
|
||||
safe = s
|
||||
|
||||
startConnect()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ abstract class InterfaceFactory(val prefix: Char) {
|
|||
factories[prefix] = this
|
||||
}
|
||||
|
||||
abstract fun createInterface(service: RadioInterfaceService, usbRepository: UsbRepository, rest: String): IRadioInterface
|
||||
abstract fun createInterface(context: Context, service: RadioInterfaceService, usbRepository: UsbRepository, rest: String): IRadioInterface
|
||||
|
||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||
open fun addressValid(context: Context, usbRepository: UsbRepository, rest: String): Boolean = true
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import okhttp3.internal.toHexString
|
|||
class MockInterface(private val service: RadioInterfaceService) : Logging, IRadioInterface {
|
||||
companion object : Logging, InterfaceFactory('m') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||
rest: String
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
|
||||
class NopInterface : IRadioInterface {
|
||||
companion object : Logging, InterfaceFactory('n') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||
rest: String
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.IBinder
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ServiceLifecycleDispatcher
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import com.geeksville.android.BinaryLogFile
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.IRadioInterfaceService
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.mesh.service.EXTRA_CONNECTED
|
||||
import com.geeksville.mesh.service.EXTRA_PAYLOAD
|
||||
import com.geeksville.mesh.service.EXTRA_PERMANENT
|
||||
import com.geeksville.mesh.service.prefix
|
||||
import com.geeksville.util.anonymize
|
||||
import com.geeksville.util.ignoreException
|
||||
import com.geeksville.util.toRemoteExceptions
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
|
|
@ -38,21 +37,32 @@ import javax.inject.Inject
|
|||
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
|
||||
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class RadioInterfaceService : Service(), Logging {
|
||||
class RadioInterfaceService @Inject constructor(
|
||||
private val context: Application,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
private val processLifecycle: Lifecycle,
|
||||
private val usbRepository: UsbRepository
|
||||
): Logging {
|
||||
|
||||
// The following is due to the fact that AIDL prevents us from extending from `LifecycleService`:
|
||||
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleDispatcher.lifecycle }
|
||||
private val lifecycleDispatcher: ServiceLifecycleDispatcher by lazy {
|
||||
ServiceLifecycleDispatcher(lifecycleOwner)
|
||||
private val _connectionState = MutableStateFlow(RadioServiceConnectionState())
|
||||
val connectionState = _connectionState.asStateFlow()
|
||||
|
||||
private val _receivedData = MutableSharedFlow<ByteArray>()
|
||||
val receivedData: SharedFlow<ByteArray> = _receivedData
|
||||
|
||||
init {
|
||||
processLifecycle.coroutineScope.launch {
|
||||
bluetoothRepository.state.collect { state ->
|
||||
if (state.enabled) {
|
||||
startInterface()
|
||||
} else {
|
||||
stopInterface()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var bluetoothRepository: BluetoothRepository
|
||||
|
||||
@Inject
|
||||
lateinit var usbRepository: UsbRepository
|
||||
|
||||
companion object : Logging {
|
||||
/**
|
||||
* The RECEIVED_FROMRADIO
|
||||
|
|
@ -133,9 +143,6 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
return address
|
||||
}
|
||||
|
||||
/// If our service is currently running, this pointer can be used to reach it (in case setBondedDeviceAddress is called)
|
||||
private var runningService: RadioInterfaceService? = null
|
||||
}
|
||||
|
||||
private val logSends = false
|
||||
|
|
@ -161,10 +168,12 @@ class RadioInterfaceService : Service(), Logging {
|
|||
|
||||
private fun broadcastConnectionChanged(isConnected: Boolean, isPermanent: Boolean) {
|
||||
debug("Broadcasting connection=$isConnected")
|
||||
val intent = Intent(RADIO_CONNECTED_ACTION)
|
||||
intent.putExtra(EXTRA_CONNECTED, isConnected)
|
||||
intent.putExtra(EXTRA_PERMANENT, isPermanent)
|
||||
sendBroadcast(intent)
|
||||
|
||||
processLifecycle.coroutineScope.launch(dispatchers.default) {
|
||||
_connectionState.emit(
|
||||
RadioServiceConnectionState(isConnected, isPermanent)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a packet/command out the radio link, this routine can block if it needs to
|
||||
|
|
@ -181,10 +190,9 @@ class RadioInterfaceService : Service(), Logging {
|
|||
|
||||
// ignoreException { debug("FromRadio: ${MeshProtos.FromRadio.parseFrom(p)}") }
|
||||
|
||||
broadcastReceivedFromRadio(
|
||||
this,
|
||||
p
|
||||
)
|
||||
processLifecycle.coroutineScope.launch(dispatchers.io) {
|
||||
_receivedData.emit(p)
|
||||
}
|
||||
}
|
||||
|
||||
fun onConnect() {
|
||||
|
|
@ -201,48 +209,12 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
runningService = this
|
||||
lifecycleDispatcher.onServicePreSuperOnCreate()
|
||||
super.onCreate()
|
||||
|
||||
lifecycleOwner.lifecycle.coroutineScope.launch {
|
||||
bluetoothRepository.state.collect { state ->
|
||||
if (state.enabled) {
|
||||
startInterface()
|
||||
} else {
|
||||
stopInterface()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
lifecycleDispatcher.onServicePreSuperOnStart()
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopInterface()
|
||||
serviceScope.cancel("Destroying RadioInterface")
|
||||
runningService = null
|
||||
lifecycleDispatcher.onServicePreSuperOnDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
lifecycleDispatcher.onServicePreSuperOnBind()
|
||||
return binder
|
||||
}
|
||||
|
||||
|
||||
/** Start our configured interface (if it isn't already running) */
|
||||
private fun startInterface() {
|
||||
if (radioIf !is NopInterface)
|
||||
warn("Can't start interface - $radioIf is already running")
|
||||
else {
|
||||
val address = getBondedDeviceAddress(this, usbRepository)
|
||||
val address = getBondedDeviceAddress(context, usbRepository)
|
||||
if (address == null)
|
||||
warn("No bonded mesh radio, can't start interface")
|
||||
else {
|
||||
|
|
@ -250,14 +222,14 @@ class RadioInterfaceService : Service(), Logging {
|
|||
isStarted = true
|
||||
|
||||
if (logSends)
|
||||
sentPacketsLog = BinaryLogFile(this, "sent_log.pb")
|
||||
sentPacketsLog = BinaryLogFile(context, "sent_log.pb")
|
||||
if (logReceives)
|
||||
receivedPacketsLog = BinaryLogFile(this, "receive_log.pb")
|
||||
receivedPacketsLog = BinaryLogFile(context, "receive_log.pb")
|
||||
|
||||
val c = address[0]
|
||||
val rest = address.substring(1)
|
||||
radioIf =
|
||||
InterfaceFactory.getFactory(c)?.createInterface(this, usbRepository, rest) ?: NopInterface()
|
||||
InterfaceFactory.getFactory(c)?.createInterface(context, this, usbRepository, rest) ?: NopInterface()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -291,7 +263,7 @@ class RadioInterfaceService : Service(), Logging {
|
|||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private fun setBondedDeviceAddress(address: String?): Boolean {
|
||||
return if (getBondedDeviceAddress(this, usbRepository) == address && isStarted) {
|
||||
return if (getBondedDeviceAddress(context, usbRepository) == address && isStarted) {
|
||||
warn("Ignoring setBondedDevice ${address.anonymize}, because we are already using that device")
|
||||
false
|
||||
} else {
|
||||
|
|
@ -309,7 +281,7 @@ class RadioInterfaceService : Service(), Logging {
|
|||
|
||||
debug("Setting bonded device to ${address.anonymize}")
|
||||
|
||||
getPrefs(this).edit(commit = true) {
|
||||
getPrefs(context).edit(commit = true) {
|
||||
if (address == null)
|
||||
this.remove(DEVADDR_KEY)
|
||||
else
|
||||
|
|
@ -322,23 +294,20 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
private val binder = object : IRadioInterfaceService.Stub() {
|
||||
fun setDeviceAddress(deviceAddr: String?): Boolean = toRemoteExceptions {
|
||||
setBondedDeviceAddress(deviceAddr)
|
||||
}
|
||||
|
||||
override fun setDeviceAddress(deviceAddr: String?): Boolean = toRemoteExceptions {
|
||||
setBondedDeviceAddress(deviceAddr)
|
||||
}
|
||||
/** If the service is not currently connected to the radio, try to connect now. At boot the radio interface service will
|
||||
* not connect to a radio until this call is received. */
|
||||
fun connect() = toRemoteExceptions {
|
||||
// We don't start actually talking to our device until MeshService binds to us - this prevents
|
||||
// broadcasting connection events before MeshService is ready to receive them
|
||||
startInterface()
|
||||
}
|
||||
|
||||
/** If the service is not currently connected to the radio, try to connect now. At boot the radio interface service will
|
||||
* not connect to a radio until this call is received. */
|
||||
override fun connect() = toRemoteExceptions {
|
||||
// We don't start actually talking to our device until MeshService binds to us - this prevents
|
||||
// broadcasting connection events before MeshService is ready to receive them
|
||||
startInterface()
|
||||
}
|
||||
|
||||
override fun sendToRadio(a: ByteArray) {
|
||||
// Do this in the IO thread because it might take a while (and we don't care about the result code)
|
||||
serviceScope.handledLaunch { handleSendToRadio(a) }
|
||||
}
|
||||
fun sendToRadio(a: ByteArray) {
|
||||
// Do this in the IO thread because it might take a while (and we don't care about the result code)
|
||||
serviceScope.handledLaunch { handleSendToRadio(a) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
data class RadioServiceConnectionState(
|
||||
val isConnected: Boolean = false,
|
||||
val isPermanent: Boolean = false
|
||||
)
|
||||
|
|
@ -19,6 +19,7 @@ class SerialInterface(
|
|||
StreamInterface(service), Logging {
|
||||
companion object : Logging, InterfaceFactory('s') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository,
|
||||
rest: String
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.util.Exceptions
|
||||
|
|
@ -18,6 +19,7 @@ class TCPInterface(service: RadioInterfaceService, private val address: String)
|
|||
|
||||
companion object : Logging, InterfaceFactory('t') {
|
||||
override fun createInterface(
|
||||
context: Context,
|
||||
service: RadioInterfaceService,
|
||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
||||
rest: String
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue