diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index 1fbfdcad2..0114d89f5 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -78,7 +78,15 @@ A variable keepAllPackets, if set to true will suppress this behavior and instea class BluetoothInterface(val service: RadioInterfaceService, val address: String) : IRadioInterface, Logging { - companion object : Logging { + companion object : Logging, InterfaceFactory('x') { + override fun createInterface( + service: RadioInterfaceService, + rest: String + ): IRadioInterface = BluetoothInterface(service, rest) + + init { + registerFactory() + } /// this service UUID is publically visible for scanning val BTM_SERVICE_UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd") @@ -100,12 +108,12 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String fun toInterfaceName(deviceName: String) = "x$deviceName" /** Return true if this address is still acceptable. For BLE that means, still bonded */ - fun addressValid(context: Context, address: String): Boolean { + override fun addressValid(context: Context, rest: String): Boolean { val allPaired = getBluetoothAdapter(context)?.bondedDevices.orEmpty().map { it.address }.toSet() - return if (!allPaired.contains(address)) { - warn("Ignoring stale bond to ${address.anonymize}") + return if (!allPaired.contains(rest)) { + warn("Ignoring stale bond to ${rest.anonymize}") false } else true diff --git a/app/src/main/java/com/geeksville/mesh/service/InterfaceFactory.kt b/app/src/main/java/com/geeksville/mesh/service/InterfaceFactory.kt new file mode 100644 index 000000000..ec178df5e --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/service/InterfaceFactory.kt @@ -0,0 +1,23 @@ +package com.geeksville.mesh.service + +import android.content.Context + +/** + * A base class for the singleton factories that make interfaces. One instance per interface type + */ +abstract class InterfaceFactory(val prefix: Char) { + companion object { + private val factories = mutableMapOf() + + fun getFactory(l: Char) = factories.get(l) + } + + protected fun registerFactory() { + factories[prefix] = this + } + + abstract fun createInterface(service: RadioInterfaceService, rest: String): IRadioInterface + + /** Return true if this address is still acceptable. For BLE that means, still bonded */ + open fun addressValid(context: Context, rest: String): Boolean = true +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt index 80d4495be..af4f58faf 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt @@ -8,9 +8,15 @@ import okhttp3.internal.toHexString /** A simulated interface that is used for testing in the simulator */ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadioInterface { - companion object : Logging { + companion object : Logging, InterfaceFactory('m') { + override fun createInterface( + service: RadioInterfaceService, + rest: String + ): IRadioInterface = MockInterface(service) - const val interfaceName = "m" + init { + registerFactory() + } } private var messageCount = 50 diff --git a/app/src/main/java/com/geeksville/mesh/service/NopInterface.kt b/app/src/main/java/com/geeksville/mesh/service/NopInterface.kt index f4e462349..37f3b8260 100644 --- a/app/src/main/java/com/geeksville/mesh/service/NopInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/NopInterface.kt @@ -1,7 +1,20 @@ package com.geeksville.mesh.service +import com.geeksville.android.Logging + class NopInterface : IRadioInterface { - override fun handleSendToRadio(p: ByteArray) { + companion object : Logging, InterfaceFactory('n') { + override fun createInterface( + service: RadioInterfaceService, + rest: String + ): IRadioInterface = NopInterface() + + init { + registerFactory() + } + } + + override fun handleSendToRadio(p: ByteArray) { } override fun close() { diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index e1a637e9c..586f58495 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -78,7 +78,7 @@ class RadioInterfaceService : Service(), Logging { // 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 && isMockInterfaceAvailable(context)) - address = MockInterface.interfaceName + address = MockInterface.prefix.toString() return address } @@ -104,13 +104,7 @@ class RadioInterfaceService : Service(), Logging { if (address != null) { val c = address[0] val rest = address.substring(1) - val isValid = when (c) { - 'x' -> BluetoothInterface.addressValid(context, rest) - 's' -> SerialInterface.addressValid(context, rest) - 'n' -> true - 'm' -> true - else -> TODO("Unexpected interface type $c") - } + val isValid = InterfaceFactory.getFactory(c)?.addressValid(context, rest) ?: false if (!isValid) return null } @@ -131,8 +125,7 @@ class RadioInterfaceService : Service(), Logging { */ var serviceScope = CoroutineScope(Dispatchers.IO + Job()) - private val nopIf = NopInterface() - private var radioIf: IRadioInterface = nopIf + private var radioIf: IRadioInterface = NopInterface() /** true if we have started our interface * @@ -217,7 +210,7 @@ class RadioInterfaceService : Service(), Logging { /** Start our configured interface (if it isn't already running) */ private fun startInterface() { - if (radioIf != nopIf) + if (radioIf !is NopInterface) warn("Can't start interface - $radioIf is already running") else { val address = getBondedDeviceAddress(this) @@ -234,26 +227,17 @@ class RadioInterfaceService : Service(), Logging { val c = address[0] val rest = address.substring(1) - radioIf = when (c) { - 'x' -> BluetoothInterface(this, rest) - 's' -> SerialInterface(this, rest) - 'm' -> MockInterface(this) - 'n' -> nopIf - else -> { - errormsg("Unexpected radio interface type") - nopIf - } - } + radioIf = InterfaceFactory.getFactory(c)?.createInterface(this, rest) ?: + NopInterface() } } } - private fun stopInterface() { val r = radioIf info("stopping interface $r") isStarted = false - radioIf = nopIf + radioIf = NopInterface() r.close() // cancel any old jobs and get ready for the new ones @@ -266,7 +250,7 @@ class RadioInterfaceService : Service(), Logging { receivedPacketsLog.close() // Don't broadcast disconnects if we were just using the nop device - if (r != nopIf) + if (r !is NopInterface) onDisconnect(isPermanent = true) // Tell any clients we are now offline } diff --git a/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt b/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt index 5912a255b..5710f596c 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SerialInterface.kt @@ -21,7 +21,15 @@ import com.hoho.android.usbserial.util.SerialInputOutputManager */ class SerialInterface(service: RadioInterfaceService, private val address: String) : StreamInterface(service), Logging, SerialInputOutputManager.Listener { - companion object : Logging { + companion object : Logging, InterfaceFactory('s') { + override fun createInterface( + service: RadioInterfaceService, + rest: String + ): IRadioInterface = SerialInterface(service, rest) + + init { + registerFactory() + } /** * according to https://stackoverflow.com/questions/12388914/usb-device-access-pop-up-suppression/15151075#15151075 @@ -41,14 +49,14 @@ class SerialInterface(service: RadioInterfaceService, private val address: Strin return drivers } - fun addressValid(context: Context, rest: String): Boolean { + override fun addressValid(context: Context, rest: String): Boolean { findSerial(context, rest)?.let { d -> return assumePermission || context.usbManager.hasPermission(d.device) } return false } - fun findSerial(context: Context, rest: String): UsbSerialDriver? { + private fun findSerial(context: Context, rest: String): UsbSerialDriver? { val drivers = findDrivers(context) return if (drivers.isEmpty()) @@ -61,7 +69,7 @@ class SerialInterface(service: RadioInterfaceService, private val address: Strin private var uart: UsbSerialDriver? = null private var ioManager: SerialInputOutputManager? = null - var usbReceiver = object : BroadcastReceiver() { + private var usbReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) = exceptionReporter { if (UsbManager.ACTION_USB_DEVICE_DETACHED == intent.action) { diff --git a/app/src/main/java/com/geeksville/mesh/service/TCPInterface.kt b/app/src/main/java/com/geeksville/mesh/service/TCPInterface.kt index 8b8e755a9..16f805b8c 100644 --- a/app/src/main/java/com/geeksville/mesh/service/TCPInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/TCPInterface.kt @@ -1,5 +1,6 @@ package com.geeksville.mesh.service +import com.geeksville.android.Logging import java.io.* import java.net.InetAddress import java.net.Socket @@ -9,7 +10,18 @@ import kotlin.concurrent.thread class TCPInterface(service: RadioInterfaceService, private val address: String) : StreamInterface(service) { - var socket: Socket? = null + companion object : Logging, InterfaceFactory('t') { + override fun createInterface( + service: RadioInterfaceService, + rest: String + ): IRadioInterface = TCPInterface(service, rest) + + init { + registerFactory() + } + } + + var socket: Socket? = null lateinit var outStream: OutputStream lateinit var inStream: InputStream