kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
This required creation of new interfaces in order to break the static coupling. This also allowed for the removal of some plumbing of dependencies of these implementations since they are now directly injected.pull/765/head
rodzic
121376201d
commit
a7b0d70c03
|
@ -38,7 +38,8 @@ import com.geeksville.mesh.model.UIViewModel
|
||||||
import com.geeksville.mesh.model.primaryChannel
|
import com.geeksville.mesh.model.primaryChannel
|
||||||
import com.geeksville.mesh.model.toChannelSet
|
import com.geeksville.mesh.model.toChannelSet
|
||||||
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
||||||
import com.geeksville.mesh.repository.radio.SerialInterface
|
import com.geeksville.mesh.repository.radio.InterfaceId
|
||||||
|
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||||
import com.geeksville.mesh.service.*
|
import com.geeksville.mesh.service.*
|
||||||
import com.geeksville.mesh.ui.*
|
import com.geeksville.mesh.ui.*
|
||||||
import com.geeksville.mesh.ui.map.MapFragment
|
import com.geeksville.mesh.ui.map.MapFragment
|
||||||
|
@ -121,6 +122,9 @@ class MainActivity : AppCompatActivity(), Logging {
|
||||||
@Inject
|
@Inject
|
||||||
internal lateinit var serviceRepository: ServiceRepository
|
internal lateinit var serviceRepository: ServiceRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
internal lateinit var radioInterfaceService: RadioInterfaceService
|
||||||
|
|
||||||
private val bluetoothPermissionsLauncher =
|
private val bluetoothPermissionsLauncher =
|
||||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||||
if (result.entries.all { it.value }) {
|
if (result.entries.all { it.value }) {
|
||||||
|
@ -462,9 +466,9 @@ class MainActivity : AppCompatActivity(), Logging {
|
||||||
try {
|
try {
|
||||||
usbDevice?.let { usb ->
|
usbDevice?.let { usb ->
|
||||||
debug("Switching to USB radio ${usb.deviceName}")
|
debug("Switching to USB radio ${usb.deviceName}")
|
||||||
service.setDeviceAddress(SerialInterface.toInterfaceName(usb.deviceName))
|
val address = radioInterfaceService.toInterfaceAddress(InterfaceId.SERIAL, usb.deviceName)
|
||||||
usbDevice =
|
service.setDeviceAddress(address)
|
||||||
null // Only switch once - thereafter it should be stored in settings
|
usbDevice = null // Only switch once - thereafter it should be stored in settings
|
||||||
}
|
}
|
||||||
|
|
||||||
val connectionState =
|
val connectionState =
|
||||||
|
|
|
@ -20,9 +20,8 @@ import com.geeksville.mesh.R
|
||||||
import com.geeksville.mesh.android.*
|
import com.geeksville.mesh.android.*
|
||||||
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
||||||
import com.geeksville.mesh.repository.nsd.NsdRepository
|
import com.geeksville.mesh.repository.nsd.NsdRepository
|
||||||
import com.geeksville.mesh.repository.radio.MockInterface
|
import com.geeksville.mesh.repository.radio.InterfaceId
|
||||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||||
import com.geeksville.mesh.repository.radio.SerialInterface
|
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||||
import com.geeksville.mesh.util.anonymize
|
import com.geeksville.mesh.util.anonymize
|
||||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||||
|
@ -83,10 +82,14 @@ class BTScanModel @Inject constructor(
|
||||||
device.bondState == BluetoothDevice.BOND_BONDED
|
device.bondState == BluetoothDevice.BOND_BONDED
|
||||||
)
|
)
|
||||||
|
|
||||||
class USBDeviceListEntry(usbManager: UsbManager, val usb: UsbSerialDriver) : DeviceListEntry(
|
class USBDeviceListEntry(
|
||||||
|
radioInterfaceService: RadioInterfaceService,
|
||||||
|
usbManager: UsbManager,
|
||||||
|
val usb: UsbSerialDriver,
|
||||||
|
) : DeviceListEntry(
|
||||||
usb.device.deviceName,
|
usb.device.deviceName,
|
||||||
SerialInterface.toInterfaceName(usb.device.deviceName),
|
radioInterfaceService.toInterfaceAddress(InterfaceId.SERIAL, usb.device.deviceName),
|
||||||
SerialInterface.assumePermission || usbManager.hasPermission(usb.device)
|
usbManager.hasPermission(usb.device),
|
||||||
)
|
)
|
||||||
|
|
||||||
class TCPDeviceListEntry(val service: NsdServiceInfo) : DeviceListEntry(
|
class TCPDeviceListEntry(val service: NsdServiceInfo) : DeviceListEntry(
|
||||||
|
@ -177,7 +180,7 @@ class BTScanModel @Inject constructor(
|
||||||
private fun setupScan(): Boolean {
|
private fun setupScan(): Boolean {
|
||||||
selectedAddress = radioInterfaceService.getDeviceAddress()
|
selectedAddress = radioInterfaceService.getDeviceAddress()
|
||||||
|
|
||||||
return if (MockInterface.addressValid(context, usbRepository, "")) {
|
return if (radioInterfaceService.isAddressValid(radioInterfaceService.mockInterfaceAddress)) {
|
||||||
warn("Running under emulator/test lab")
|
warn("Running under emulator/test lab")
|
||||||
|
|
||||||
val testnodes = listOf(
|
val testnodes = listOf(
|
||||||
|
@ -215,7 +218,7 @@ class BTScanModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
usbDevices.value?.forEach { (_, d) ->
|
usbDevices.value?.forEach { (_, d) ->
|
||||||
addDevice(USBDeviceListEntry(context.usbManager, d))
|
addDevice(USBDeviceListEntry(radioInterfaceService, context.usbManager, d))
|
||||||
}
|
}
|
||||||
|
|
||||||
devices.value = newDevs
|
devices.value = newDevs
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package com.geeksville.mesh.repository.radio
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.bluetooth.BluetoothGattCharacteristic
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
import android.bluetooth.BluetoothGattService
|
import android.bluetooth.BluetoothGattService
|
||||||
import android.content.Context
|
|
||||||
import com.geeksville.mesh.android.Logging
|
import com.geeksville.mesh.android.Logging
|
||||||
import com.geeksville.mesh.android.bluetoothManager
|
|
||||||
import com.geeksville.mesh.concurrent.handledLaunch
|
import com.geeksville.mesh.concurrent.handledLaunch
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
|
||||||
import com.geeksville.mesh.service.*
|
import com.geeksville.mesh.service.*
|
||||||
import com.geeksville.mesh.util.anonymize
|
import com.geeksville.mesh.util.anonymize
|
||||||
import com.geeksville.mesh.util.exceptionReporter
|
import com.geeksville.mesh.util.exceptionReporter
|
||||||
import com.geeksville.mesh.util.ignoreException
|
import com.geeksville.mesh.util.ignoreException
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -76,25 +77,15 @@ 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...
|
* 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.
|
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
|
||||||
*/
|
*/
|
||||||
class BluetoothInterface(
|
class BluetoothInterface @AssistedInject constructor(
|
||||||
val context: Context,
|
context: Application,
|
||||||
val service: RadioInterfaceService,
|
bluetoothAdapter: dagger.Lazy<BluetoothAdapter?>,
|
||||||
val address: String) : IRadioInterface,
|
private val service: RadioInterfaceService,
|
||||||
Logging {
|
@Assisted val address: String,
|
||||||
|
) : IRadioInterface, Logging {
|
||||||
|
|
||||||
companion object : Logging, InterfaceFactory('x') {
|
companion object {
|
||||||
override fun createInterface(
|
/// this service UUID is publicly visible for scanning
|
||||||
context: Context,
|
|
||||||
service: RadioInterfaceService,
|
|
||||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
|
||||||
rest: String
|
|
||||||
): IRadioInterface = BluetoothInterface(context, service, rest)
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// this service UUID is publically visible for scanning
|
|
||||||
val BTM_SERVICE_UUID: UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
|
val BTM_SERVICE_UUID: UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
|
||||||
|
|
||||||
var invalidVersion = false
|
var invalidVersion = false
|
||||||
|
@ -108,22 +99,6 @@ class BluetoothInterface(
|
||||||
val BTM_FROMNUM_CHARACTER: UUID =
|
val BTM_FROMNUM_CHARACTER: UUID =
|
||||||
UUID.fromString("ed9da18c-a800-4f66-a670-aa7547e34453")
|
UUID.fromString("ed9da18c-a800-4f66-a670-aa7547e34453")
|
||||||
|
|
||||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
|
||||||
override fun addressValid(
|
|
||||||
context: Context,
|
|
||||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
|
||||||
rest: String
|
|
||||||
): Boolean {
|
|
||||||
/// Get our bluetooth adapter (should always succeed except on emulator
|
|
||||||
val allPaired = context.bluetoothManager?.adapter?.bondedDevices.orEmpty()
|
|
||||||
.map { it.address }.toSet()
|
|
||||||
return if (!allPaired.contains(rest)) {
|
|
||||||
warn("Ignoring stale bond to ${rest.anonymize}")
|
|
||||||
false
|
|
||||||
} else
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is created in onCreate()
|
* this is created in onCreate()
|
||||||
* We do an ugly hack of keeping it in the singleton so we can share it for the rare software update case
|
* We do an ugly hack of keeping it in the singleton so we can share it for the rare software update case
|
||||||
|
@ -161,7 +136,7 @@ class BluetoothInterface(
|
||||||
init {
|
init {
|
||||||
// Note: this call does no comms, it just creates the device object (even if the
|
// Note: this call does no comms, it just creates the device object (even if the
|
||||||
// device is off/not connected)
|
// device is off/not connected)
|
||||||
val device = context.bluetoothManager?.adapter?.getRemoteDevice(address)
|
val device = bluetoothAdapter.get()?.getRemoteDevice(address)
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
info("Creating radio interface service. device=${address.anonymize}")
|
info("Creating radio interface service. device=${address.anonymize}")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating `BluetoothInterface` instances.
|
||||||
|
*/
|
||||||
|
@AssistedFactory
|
||||||
|
interface BluetoothInterfaceFactory : InterfaceFactorySpi<BluetoothInterface>
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.BluetoothAdapter
|
||||||
|
import com.geeksville.mesh.android.Logging
|
||||||
|
import com.geeksville.mesh.util.anonymize
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bluetooth backend implementation.
|
||||||
|
*/
|
||||||
|
class BluetoothInterfaceSpec @Inject constructor(
|
||||||
|
private val factory: BluetoothInterfaceFactory,
|
||||||
|
private val bluetoothAdapter: dagger.Lazy<BluetoothAdapter?>
|
||||||
|
|
||||||
|
): InterfaceSpec<BluetoothInterface>, Logging {
|
||||||
|
override fun createInterface(rest: String): BluetoothInterface {
|
||||||
|
return factory.create(rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
override fun addressValid(rest: String): Boolean {
|
||||||
|
val allPaired = bluetoothAdapter.get()?.bondedDevices.orEmpty()
|
||||||
|
.map { it.address }.toSet()
|
||||||
|
return if (!allPaired.contains(rest)) {
|
||||||
|
warn("Ignoring stale bond to ${rest.anonymize}")
|
||||||
|
false
|
||||||
|
} else
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,41 @@
|
||||||
package com.geeksville.mesh.repository.radio
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
import android.content.Context
|
import javax.inject.Inject
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
import javax.inject.Provider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for the singleton factories that make interfaces. One instance per interface type
|
* Entry point for create radio backend instances given a specific address.
|
||||||
|
*
|
||||||
|
* This class is responsible for building and dissecting radio addresses based upon
|
||||||
|
* their interface type and the "rest" of the address (which varies per implementation).
|
||||||
*/
|
*/
|
||||||
abstract class InterfaceFactory(val prefix: Char) {
|
class InterfaceFactory @Inject constructor(
|
||||||
companion object {
|
private val nopInterfaceFactory: NopInterfaceFactory,
|
||||||
private val factories = mutableMapOf<Char, InterfaceFactory>()
|
private val specMap: Map<InterfaceId, @JvmSuppressWildcards Provider<InterfaceSpec<*>>>
|
||||||
|
) {
|
||||||
fun getFactory(l: Char) = factories.get(l)
|
internal val nopInterface by lazy {
|
||||||
|
nopInterfaceFactory.create("")
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun registerFactory() {
|
fun toInterfaceAddress(interfaceId: InterfaceId, rest: String): String {
|
||||||
factories[prefix] = this
|
return "${interfaceId.id}$rest"
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun createInterface(context: Context, service: RadioInterfaceService, usbRepository: UsbRepository, rest: String): IRadioInterface
|
fun createInterface(address: String): IRadioInterface {
|
||||||
|
val (spec, rest) = splitAddress(address)
|
||||||
|
return spec?.createInterface(rest) ?: nopInterface
|
||||||
|
}
|
||||||
|
|
||||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
fun addressValid(address: String?): Boolean {
|
||||||
open fun addressValid(context: Context, usbRepository: UsbRepository, rest: String): Boolean = true
|
return address?.let {
|
||||||
|
val (spec, rest) = splitAddress(it)
|
||||||
|
spec?.addressValid(rest)
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun splitAddress(address: String): Pair<InterfaceSpec<*>?, String> {
|
||||||
|
val c = address[0].let { InterfaceId.forIdChar(it) }?.let { specMap[it]?.get() }
|
||||||
|
val rest = address.substring(1)
|
||||||
|
return Pair(c, rest)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radio interface factory service provider interface. Each radio backend implementation needs
|
||||||
|
* to have a factory to create new instances. These instances are specific to a particular
|
||||||
|
* address. This interface defines a common API across all radio interfaces for obtaining
|
||||||
|
* implementation instances.
|
||||||
|
*
|
||||||
|
* This is primarily used in conjunction with Dagger assisted injection for each backend
|
||||||
|
* interface type.
|
||||||
|
*/
|
||||||
|
interface InterfaceFactorySpi<T: IRadioInterface> {
|
||||||
|
fun create(rest: String): T
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address identifiers for all supported radio backend implementations.
|
||||||
|
*/
|
||||||
|
enum class InterfaceId(val id: Char) {
|
||||||
|
BLUETOOTH('x'),
|
||||||
|
MOCK('m'),
|
||||||
|
NOP('n'),
|
||||||
|
SERIAL('s'),
|
||||||
|
TCP('t');
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun forIdChar(id: Char): InterfaceId? {
|
||||||
|
return values().firstOrNull { it.id == id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import dagger.MapKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dagger `MapKey` implementation allowing for `InterfaceId` to be used as a map key.
|
||||||
|
*/
|
||||||
|
@MapKey
|
||||||
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class InterfaceMapKey(val value: InterfaceId)
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines the contract that all radio backend implementations must adhere to.
|
||||||
|
*/
|
||||||
|
interface InterfaceSpec<T : IRadioInterface> {
|
||||||
|
fun createInterface(rest: String): T
|
||||||
|
|
||||||
|
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||||
|
fun addressValid(rest: String): Boolean = true
|
||||||
|
}
|
|
@ -1,36 +1,17 @@
|
||||||
package com.geeksville.mesh.repository.radio
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.geeksville.mesh.android.BuildUtils
|
|
||||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
|
||||||
import com.geeksville.mesh.android.Logging
|
|
||||||
import com.geeksville.mesh.*
|
import com.geeksville.mesh.*
|
||||||
|
import com.geeksville.mesh.android.Logging
|
||||||
import com.geeksville.mesh.model.getInitials
|
import com.geeksville.mesh.model.getInitials
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
|
||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
/** A simulated interface that is used for testing in the simulator */
|
/** A simulated interface that is used for testing in the simulator */
|
||||||
class MockInterface(private val service: RadioInterfaceService) : Logging, IRadioInterface {
|
class MockInterface @AssistedInject constructor(
|
||||||
companion object : Logging, InterfaceFactory('m') {
|
private val service: RadioInterfaceService,
|
||||||
override fun createInterface(
|
@Assisted val address: String
|
||||||
context: Context,
|
) : Logging, IRadioInterface {
|
||||||
service: RadioInterfaceService,
|
|
||||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
|
||||||
rest: String
|
|
||||||
): IRadioInterface = MockInterface(service)
|
|
||||||
|
|
||||||
override fun addressValid(
|
|
||||||
context: Context,
|
|
||||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
|
||||||
rest: String
|
|
||||||
): Boolean =
|
|
||||||
BuildUtils.isEmulator || ((context.applicationContext as GeeksvilleApplication).isInTestLab)
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerFactory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var messageCount = 50
|
private var messageCount = 50
|
||||||
|
|
||||||
// an infinite sequence of ints
|
// an infinite sequence of ints
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating `MockInterface` instances.
|
||||||
|
*/
|
||||||
|
@AssistedFactory
|
||||||
|
interface MockInterfaceFactory : InterfaceFactorySpi<MockInterface>
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.geeksville.mesh.android.BuildUtils
|
||||||
|
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock interface backend implementation.
|
||||||
|
*/
|
||||||
|
class MockInterfaceSpec @Inject constructor(
|
||||||
|
private val application: Application,
|
||||||
|
private val factory: MockInterfaceFactory
|
||||||
|
): InterfaceSpec<MockInterface> {
|
||||||
|
override fun createInterface(rest: String): MockInterface {
|
||||||
|
return factory.create(rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||||
|
override fun addressValid(rest: String): Boolean =
|
||||||
|
BuildUtils.isEmulator || ((application as GeeksvilleApplication).isInTestLab)
|
||||||
|
}
|
|
@ -1,23 +1,9 @@
|
||||||
package com.geeksville.mesh.repository.radio
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
import android.content.Context
|
import dagger.assisted.Assisted
|
||||||
import com.geeksville.mesh.android.Logging
|
import dagger.assisted.AssistedInject
|
||||||
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
|
|
||||||
): IRadioInterface = NopInterface()
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerFactory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
class NopInterface @AssistedInject constructor(@Assisted val address: String) : IRadioInterface {
|
||||||
override fun handleSendToRadio(p: ByteArray) {
|
override fun handleSendToRadio(p: ByteArray) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating `NopInterface` instances.
|
||||||
|
*/
|
||||||
|
@AssistedFactory
|
||||||
|
interface NopInterfaceFactory : InterfaceFactorySpi<NopInterface>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No-op interface backend implementation.
|
||||||
|
*/
|
||||||
|
class NopInterfaceSpec @Inject constructor(
|
||||||
|
private val factory: NopInterfaceFactory
|
||||||
|
): InterfaceSpec<NopInterface> {
|
||||||
|
override fun createInterface(rest: String): NopInterface {
|
||||||
|
return factory.create(rest)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,14 +6,13 @@ import android.content.SharedPreferences
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
|
import com.geeksville.mesh.CoroutineDispatchers
|
||||||
import com.geeksville.mesh.android.BinaryLogFile
|
import com.geeksville.mesh.android.BinaryLogFile
|
||||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||||
import com.geeksville.mesh.android.Logging
|
import com.geeksville.mesh.android.Logging
|
||||||
import com.geeksville.mesh.concurrent.handledLaunch
|
import com.geeksville.mesh.concurrent.handledLaunch
|
||||||
import com.geeksville.mesh.CoroutineDispatchers
|
|
||||||
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
||||||
import com.geeksville.mesh.repository.nsd.NsdRepository
|
import com.geeksville.mesh.repository.nsd.NsdRepository
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
|
||||||
import com.geeksville.mesh.util.anonymize
|
import com.geeksville.mesh.util.anonymize
|
||||||
import com.geeksville.mesh.util.ignoreException
|
import com.geeksville.mesh.util.ignoreException
|
||||||
import com.geeksville.mesh.util.toRemoteExceptions
|
import com.geeksville.mesh.util.toRemoteExceptions
|
||||||
|
@ -45,8 +44,9 @@ class RadioInterfaceService @Inject constructor(
|
||||||
bluetoothRepository: BluetoothRepository,
|
bluetoothRepository: BluetoothRepository,
|
||||||
nsdRepository: NsdRepository,
|
nsdRepository: NsdRepository,
|
||||||
private val processLifecycle: Lifecycle,
|
private val processLifecycle: Lifecycle,
|
||||||
private val usbRepository: UsbRepository,
|
@RadioRepositoryQualifier private val prefs: SharedPreferences,
|
||||||
@RadioRepositoryQualifier private val prefs: SharedPreferences
|
private val interfaceFactory: InterfaceFactory,
|
||||||
|
private val mockInterfaceSpec: MockInterfaceSpec
|
||||||
) : Logging {
|
) : Logging {
|
||||||
|
|
||||||
private val _connectionState = MutableStateFlow(RadioServiceConnectionState())
|
private val _connectionState = MutableStateFlow(RadioServiceConnectionState())
|
||||||
|
@ -72,12 +72,16 @@ class RadioInterfaceService @Inject constructor(
|
||||||
private lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate
|
private lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate
|
||||||
private lateinit var receivedPacketsLog: BinaryLogFile
|
private lateinit var receivedPacketsLog: BinaryLogFile
|
||||||
|
|
||||||
|
val mockInterfaceAddress: String by lazy {
|
||||||
|
toInterfaceAddress(InterfaceId.MOCK, "")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We recreate this scope each time we stop an interface
|
* We recreate this scope each time we stop an interface
|
||||||
*/
|
*/
|
||||||
var serviceScope = CoroutineScope(Dispatchers.IO + Job())
|
var serviceScope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
|
|
||||||
private var radioIf: IRadioInterface = NopInterface()
|
private var radioIf: IRadioInterface = NopInterface("")
|
||||||
|
|
||||||
/** true if we have started our interface
|
/** true if we have started our interface
|
||||||
*
|
*
|
||||||
|
@ -102,18 +106,17 @@ class RadioInterfaceService @Inject constructor(
|
||||||
|
|
||||||
companion object : Logging {
|
companion object : Logging {
|
||||||
const val DEVADDR_KEY = "devAddr2" // the new name for devaddr
|
const val DEVADDR_KEY = "devAddr2" // the new name for devaddr
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
/**
|
||||||
/// We keep this var alive so that the following factory objects get created and not stripped during the android build
|
* Constructs a full radio address for the specific interface type.
|
||||||
val factories = arrayOf<InterfaceFactory>(
|
*/
|
||||||
BluetoothInterface,
|
fun toInterfaceAddress(interfaceId: InterfaceId, rest: String): String {
|
||||||
SerialInterface,
|
return interfaceFactory.toInterfaceAddress(interfaceId, rest)
|
||||||
TCPInterface,
|
}
|
||||||
MockInterface,
|
|
||||||
NopInterface
|
fun isAddressValid(address: String?): Boolean {
|
||||||
)
|
return interfaceFactory.addressValid(address)
|
||||||
info("Using ${factories.size} interface factories")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return the device we are configured to use, or null for none
|
/** Return the device we are configured to use, or null for none
|
||||||
|
@ -129,8 +132,8 @@ class RadioInterfaceService @Inject constructor(
|
||||||
var address = prefs.getString(DEVADDR_KEY, null)
|
var address = prefs.getString(DEVADDR_KEY, null)
|
||||||
|
|
||||||
// If we are running on the emulator we default to the mock interface, so we can have some data to show to the user
|
// If we are running on the emulator we default to the mock interface, so we can have some data to show to the user
|
||||||
if (address == null && MockInterface.addressValid(context, usbRepository, ""))
|
if (address == null && mockInterfaceSpec.addressValid(""))
|
||||||
address = MockInterface.prefix.toString()
|
address = "${InterfaceId.MOCK.id}"
|
||||||
|
|
||||||
return address
|
return address
|
||||||
}
|
}
|
||||||
|
@ -146,17 +149,11 @@ class RadioInterfaceService @Inject constructor(
|
||||||
fun getBondedDeviceAddress(): String? {
|
fun getBondedDeviceAddress(): String? {
|
||||||
// If the user has unpaired our device, treat things as if we don't have one
|
// If the user has unpaired our device, treat things as if we don't have one
|
||||||
val address = getDeviceAddress()
|
val address = getDeviceAddress()
|
||||||
|
return if (interfaceFactory.addressValid(address)) {
|
||||||
/// Interfaces can filter addresses to indicate that address is no longer acceptable
|
address
|
||||||
if (address != null) {
|
} else {
|
||||||
val c = address[0]
|
null
|
||||||
val rest = address.substring(1)
|
|
||||||
val isValid = InterfaceFactory.getFactory(c)
|
|
||||||
?.addressValid(context, usbRepository, rest) ?: false
|
|
||||||
if (!isValid)
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
return address
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun broadcastConnectionChanged(isConnected: Boolean, isPermanent: Boolean) {
|
private fun broadcastConnectionChanged(isConnected: Boolean, isPermanent: Boolean) {
|
||||||
|
@ -219,10 +216,7 @@ class RadioInterfaceService @Inject constructor(
|
||||||
if (logReceives)
|
if (logReceives)
|
||||||
receivedPacketsLog = BinaryLogFile(context, "receive_log.pb")
|
receivedPacketsLog = BinaryLogFile(context, "receive_log.pb")
|
||||||
|
|
||||||
val c = address[0]
|
radioIf = interfaceFactory.createInterface(address)
|
||||||
val rest = address.substring(1)
|
|
||||||
radioIf =
|
|
||||||
InterfaceFactory.getFactory(c)?.createInterface(context, this, usbRepository, rest) ?: NopInterface()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,7 +225,7 @@ class RadioInterfaceService @Inject constructor(
|
||||||
val r = radioIf
|
val r = radioIf
|
||||||
info("stopping interface $r")
|
info("stopping interface $r")
|
||||||
isStarted = false
|
isStarted = false
|
||||||
radioIf = NopInterface()
|
radioIf = interfaceFactory.nopInterface
|
||||||
r.close()
|
r.close()
|
||||||
|
|
||||||
// cancel any old jobs and get ready for the new ones
|
// cancel any old jobs and get ready for the new ones
|
||||||
|
|
|
@ -3,17 +3,42 @@ package com.geeksville.mesh.repository.radio
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import dagger.multibindings.IntoMap
|
||||||
|
import dagger.multibindings.Multibinds
|
||||||
|
|
||||||
|
@Suppress("unused") // Used by hilt
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object RadioRepositoryModule {
|
abstract class RadioRepositoryModule {
|
||||||
@Provides
|
|
||||||
@RadioRepositoryQualifier
|
@Multibinds
|
||||||
fun provideSharedPreferences(application: Application): SharedPreferences {
|
abstract fun interfaceMap(): Map<InterfaceId, @JvmSuppressWildcards InterfaceSpec<*>>
|
||||||
return application.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
|
|
||||||
|
@[Binds IntoMap InterfaceMapKey(InterfaceId.BLUETOOTH)]
|
||||||
|
abstract fun bindBluetoothInterfaceSpec(spec: BluetoothInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||||
|
|
||||||
|
@[Binds IntoMap InterfaceMapKey(InterfaceId.MOCK)]
|
||||||
|
abstract fun bindMockInterfaceSpec(spec: MockInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||||
|
|
||||||
|
@[Binds IntoMap InterfaceMapKey(InterfaceId.NOP)]
|
||||||
|
abstract fun bindNopInterfaceSpec(spec: NopInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||||
|
|
||||||
|
@[Binds IntoMap InterfaceMapKey(InterfaceId.SERIAL)]
|
||||||
|
abstract fun bindSerialInterfaceSpec(spec: SerialInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||||
|
|
||||||
|
@[Binds IntoMap InterfaceMapKey(InterfaceId.TCP)]
|
||||||
|
abstract fun bindTCPInterfaceSpec(spec: TCPInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Provides
|
||||||
|
@RadioRepositoryQualifier
|
||||||
|
fun provideSharedPreferences(application: Application): SharedPreferences {
|
||||||
|
return application.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,67 +1,22 @@
|
||||||
package com.geeksville.mesh.repository.radio
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.geeksville.mesh.android.Logging
|
import com.geeksville.mesh.android.Logging
|
||||||
import com.geeksville.mesh.android.usbManager
|
|
||||||
import com.geeksville.mesh.repository.usb.SerialConnection
|
import com.geeksville.mesh.repository.usb.SerialConnection
|
||||||
import com.geeksville.mesh.repository.usb.SerialConnectionListener
|
import com.geeksville.mesh.repository.usb.SerialConnectionListener
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface that assumes we are talking to a meshtastic device via USB serial
|
* An interface that assumes we are talking to a meshtastic device via USB serial
|
||||||
*/
|
*/
|
||||||
class SerialInterface(
|
class SerialInterface @AssistedInject constructor(
|
||||||
service: RadioInterfaceService,
|
service: RadioInterfaceService,
|
||||||
|
private val serialInterfaceSpec: SerialInterfaceSpec,
|
||||||
private val usbRepository: UsbRepository,
|
private val usbRepository: UsbRepository,
|
||||||
private val address: String) :
|
@Assisted private val address: String,
|
||||||
StreamInterface(service), Logging {
|
) : StreamInterface(service), Logging {
|
||||||
companion object : Logging, InterfaceFactory('s') {
|
|
||||||
override fun createInterface(
|
|
||||||
context: Context,
|
|
||||||
service: RadioInterfaceService,
|
|
||||||
usbRepository: UsbRepository,
|
|
||||||
rest: String
|
|
||||||
): IRadioInterface = SerialInterface(service, usbRepository, rest)
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* according to https://stackoverflow.com/questions/12388914/usb-device-access-pop-up-suppression/15151075#15151075
|
|
||||||
* we should never ask for USB permissions ourselves, instead we should rely on the external dialog printed by the system. If
|
|
||||||
* we do that the system will remember we have accesss
|
|
||||||
*/
|
|
||||||
const val assumePermission = false
|
|
||||||
|
|
||||||
fun toInterfaceName(deviceName: String) = "s$deviceName"
|
|
||||||
|
|
||||||
override fun addressValid(
|
|
||||||
context: Context,
|
|
||||||
usbRepository: UsbRepository,
|
|
||||||
rest: String
|
|
||||||
): Boolean {
|
|
||||||
usbRepository.serialDevicesWithDrivers.value.filterValues {
|
|
||||||
assumePermission || context.usbManager.hasPermission(it.device)
|
|
||||||
}
|
|
||||||
findSerial(usbRepository, rest)?.let { d ->
|
|
||||||
return assumePermission || context.usbManager.hasPermission(d.device)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findSerial(usbRepository: UsbRepository, rest: String): UsbSerialDriver? {
|
|
||||||
val deviceMap = usbRepository.serialDevicesWithDrivers.value
|
|
||||||
return if (deviceMap.containsKey(rest)) {
|
|
||||||
deviceMap[rest]!!
|
|
||||||
} else {
|
|
||||||
deviceMap.map { (_, driver) -> driver }.firstOrNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var connRef = AtomicReference<SerialConnection?>()
|
private var connRef = AtomicReference<SerialConnection?>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -74,7 +29,7 @@ class SerialInterface(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun connect() {
|
override fun connect() {
|
||||||
val device = findSerial(usbRepository, address)
|
val device = serialInterfaceSpec.findSerial(address)
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
errormsg("Can't find device")
|
errormsg("Can't find device")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating `SerialInterface` instances.
|
||||||
|
*/
|
||||||
|
@AssistedFactory
|
||||||
|
interface SerialInterfaceFactory : InterfaceFactorySpi<SerialInterface>
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.geeksville.mesh.android.usbManager
|
||||||
|
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||||
|
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serial/USB interface backend implementation.
|
||||||
|
*/
|
||||||
|
class SerialInterfaceSpec @Inject constructor(
|
||||||
|
private val factory: SerialInterfaceFactory,
|
||||||
|
private val context: Application,
|
||||||
|
private val usbRepository: UsbRepository,
|
||||||
|
): InterfaceSpec<SerialInterface> {
|
||||||
|
override fun createInterface(rest: String): SerialInterface {
|
||||||
|
return factory.create(rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addressValid(
|
||||||
|
rest: String
|
||||||
|
): Boolean {
|
||||||
|
usbRepository.serialDevicesWithDrivers.value.filterValues {
|
||||||
|
context.usbManager.hasPermission(it.device)
|
||||||
|
}
|
||||||
|
findSerial(rest)?.let { d ->
|
||||||
|
return context.usbManager.hasPermission(d.device)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun findSerial(rest: String): UsbSerialDriver? {
|
||||||
|
val deviceMap = usbRepository.serialDevicesWithDrivers.value
|
||||||
|
return if (deviceMap.containsKey(rest)) {
|
||||||
|
deviceMap[rest]!!
|
||||||
|
} else {
|
||||||
|
deviceMap.map { (_, driver) -> driver }.firstOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package com.geeksville.mesh.repository.radio
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.geeksville.mesh.android.Logging
|
import com.geeksville.mesh.android.Logging
|
||||||
import com.geeksville.mesh.concurrent.handledLaunch
|
import com.geeksville.mesh.concurrent.handledLaunch
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
|
||||||
import com.geeksville.mesh.util.Exceptions
|
import com.geeksville.mesh.util.Exceptions
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -16,21 +16,12 @@ import java.net.InetAddress
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
|
|
||||||
class TCPInterface(service: RadioInterfaceService, private val address: String) :
|
class TCPInterface @AssistedInject constructor(
|
||||||
StreamInterface(service) {
|
service: RadioInterfaceService,
|
||||||
|
@Assisted private val address: String,
|
||||||
companion object : Logging, InterfaceFactory('t') {
|
) : StreamInterface(service), Logging {
|
||||||
override fun createInterface(
|
|
||||||
context: Context,
|
|
||||||
service: RadioInterfaceService,
|
|
||||||
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
|
|
||||||
rest: String
|
|
||||||
): IRadioInterface = TCPInterface(service, rest)
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
const val MAX_RETRIES_ALLOWED = Int.MAX_VALUE
|
const val MAX_RETRIES_ALLOWED = Int.MAX_VALUE
|
||||||
const val MIN_BACKOFF_MILLIS = 1 * 1000L // 1 second
|
const val MIN_BACKOFF_MILLIS = 1 * 1000L // 1 second
|
||||||
const val MAX_BACKOFF_MILLIS = 5 * 60 * 1000L // 5 minutes
|
const val MAX_BACKOFF_MILLIS = 5 * 60 * 1000L // 5 minutes
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating `TCPInterface` instances.
|
||||||
|
*/
|
||||||
|
@AssistedFactory
|
||||||
|
interface TCPInterfaceFactory : InterfaceFactorySpi<TCPInterface>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.geeksville.mesh.repository.radio
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCP interface backend implementation.
|
||||||
|
*/
|
||||||
|
class TCPInterfaceSpec @Inject constructor(
|
||||||
|
private val factory: TCPInterfaceFactory
|
||||||
|
): InterfaceSpec<TCPInterface> {
|
||||||
|
override fun createInterface(rest: String): TCPInterface {
|
||||||
|
return factory.create(rest)
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,13 +29,10 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.geeksville.mesh.analytics.DataPair
|
import com.geeksville.mesh.ConfigProtos
|
||||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
|
||||||
import com.geeksville.mesh.android.Logging
|
|
||||||
import com.geeksville.mesh.android.hideKeyboard
|
|
||||||
import com.geeksville.mesh.MainActivity
|
import com.geeksville.mesh.MainActivity
|
||||||
import com.geeksville.mesh.R
|
import com.geeksville.mesh.R
|
||||||
import com.geeksville.mesh.ConfigProtos
|
import com.geeksville.mesh.analytics.DataPair
|
||||||
import com.geeksville.mesh.ModuleConfigProtos
|
import com.geeksville.mesh.ModuleConfigProtos
|
||||||
import com.geeksville.mesh.android.*
|
import com.geeksville.mesh.android.*
|
||||||
import com.geeksville.mesh.databinding.SettingsFragmentBinding
|
import com.geeksville.mesh.databinding.SettingsFragmentBinding
|
||||||
|
@ -44,8 +41,7 @@ import com.geeksville.mesh.model.BluetoothViewModel
|
||||||
import com.geeksville.mesh.model.UIViewModel
|
import com.geeksville.mesh.model.UIViewModel
|
||||||
import com.geeksville.mesh.model.getInitials
|
import com.geeksville.mesh.model.getInitials
|
||||||
import com.geeksville.mesh.repository.location.LocationRepository
|
import com.geeksville.mesh.repository.location.LocationRepository
|
||||||
import com.geeksville.mesh.repository.radio.MockInterface
|
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
|
||||||
import com.geeksville.mesh.service.MeshService
|
import com.geeksville.mesh.service.MeshService
|
||||||
import com.geeksville.mesh.service.SoftwareUpdateService
|
import com.geeksville.mesh.service.SoftwareUpdateService
|
||||||
import com.geeksville.mesh.util.PendingIntentCompat
|
import com.geeksville.mesh.util.PendingIntentCompat
|
||||||
|
@ -71,7 +67,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
private val model: UIViewModel by activityViewModels()
|
private val model: UIViewModel by activityViewModels()
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
internal lateinit var usbRepository: UsbRepository
|
internal lateinit var radioInterfaceServiceLazy: dagger.Lazy<RadioInterfaceService>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
internal lateinit var locationRepository: LocationRepository
|
internal lateinit var locationRepository: LocationRepository
|
||||||
|
@ -506,7 +502,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
// If we are running on an emulator, always leave this message showing so we can test the worst case layout
|
// If we are running on an emulator, always leave this message showing so we can test the worst case layout
|
||||||
val curRadio = scanModel.selectedAddress
|
val curRadio = scanModel.selectedAddress
|
||||||
|
|
||||||
if (curRadio != null && !MockInterface.addressValid(requireContext(), usbRepository, "")) {
|
val radioInterfaceService = radioInterfaceServiceLazy.get()
|
||||||
|
if (curRadio != null && !radioInterfaceService.isAddressValid(radioInterfaceService.mockInterfaceAddress)) {
|
||||||
binding.warningNotPaired.visibility = View.GONE
|
binding.warningNotPaired.visibility = View.GONE
|
||||||
} else if (bluetoothViewModel.enabled.value == true) {
|
} else if (bluetoothViewModel.enabled.value == true) {
|
||||||
binding.warningNotPaired.visibility = View.VISIBLE
|
binding.warningNotPaired.visibility = View.VISIBLE
|
||||||
|
|
Ładowanie…
Reference in New Issue