Initial step in refactoring RadioInterfaceService for dependency injection

Extracts USB device management into a `UsbRepository`.

In order for `SerialInterface to gain access to this prior to
the `RadioInterfaceService` being fully natively dependency
injected, all `InterfaceFactory` implementations needed
to be modified to accept the `UsbRepository` via argument.  This
will go away in a future PR.

Changed `assumePermission` constant to `false` as it was preventing
the request for permission from occurring, breaking serial connectivity.

Minor improvement: SerialInterface re-bonding by device name is
now supported.
master
Mike Cumings 2022-04-08 11:34:44 -07:00
rodzic 26b6081e9c
commit dd41527bbc
17 zmienionych plików z 293 dodań i 102 usunięć

Wyświetl plik

@ -88,6 +88,7 @@ android {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += [ '-Xopt-in=kotlin.RequiresOptIn' ]
}
lint {
abortOnError false

Wyświetl plik

@ -10,11 +10,7 @@ import android.content.pm.PackageManager
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.RemoteException
import android.os.*
import android.text.method.LinkMovementMethod
import android.view.Menu
import android.view.MenuItem
@ -23,7 +19,6 @@ import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
@ -44,6 +39,7 @@ import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.ChannelSet
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.repository.usb.UsbRepository
import com.geeksville.mesh.service.*
import com.geeksville.mesh.ui.*
import com.geeksville.util.Exceptions
@ -66,6 +62,7 @@ import kotlinx.coroutines.cancel
import java.nio.charset.Charset
import java.text.DateFormat
import java.util.*
import javax.inject.Inject
/*
UI design
@ -138,6 +135,9 @@ class MainActivity : BaseActivity(), Logging,
private val bluetoothViewModel: BluetoothViewModel by viewModels()
val model: UIViewModel by viewModels()
@Inject
internal lateinit var usbRepository: UsbRepository
data class TabInfo(val text: String, val icon: Int, val content: Fragment)
// private val tabIndexes = generateSequence(0) { it + 1 } FIXME, instead do withIndex or zip? to get the ids below, also stop duplicating strings
@ -974,7 +974,7 @@ class MainActivity : BaseActivity(), Logging,
bluetoothViewModel.enabled.observe(this) { enabled ->
if (!enabled) {
// Ask to start bluetooth if no USB devices are visible
val hasUSB = SerialInterface.findDrivers(this).isNotEmpty()
val hasUSB = usbRepository.serialDevicesWithDrivers.value.isNotEmpty()
if (!isInTestLab && !hasUSB) {
if (hasConnectPermission()) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
@ -991,7 +991,7 @@ class MainActivity : BaseActivity(), Logging,
errormsg("Bind of MeshService failed")
}
val bonded = RadioInterfaceService.getBondedDeviceAddress(this) != null
val bonded = RadioInterfaceService.getBondedDeviceAddress(this, usbRepository) != null
if (!bonded && usbDevice == null) // we will handle USB later
showSettingsPage()
}

Wyświetl plik

@ -0,0 +1,25 @@
package com.geeksville.mesh.repository.usb
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver
import com.hoho.android.usbserial.driver.ProbeTable
import com.hoho.android.usbserial.driver.UsbSerialProber
import dagger.Reusable
import javax.inject.Inject
import javax.inject.Provider
/**
* Creates a probe table for the USB driver. This augments the default device-to-driver
* mappings with additional known working configurations. See this package's README for
* more info.
*/
@Reusable
class ProbeTableProvider @Inject constructor() : Provider<ProbeTable> {
override fun get(): ProbeTable {
return UsbSerialProber.getDefaultProbeTable().apply {
// RAK 4631:
addProduct(9114, 32809, CdcAcmSerialDriver::class.java)
// LilyGo TBeam v1.1:
addProduct(6790, 21972, CdcAcmSerialDriver::class.java)
}
}
}

Wyświetl plik

@ -0,0 +1,23 @@
# USB Module
This module provides a repository for acessing USB devices.
## Device Support
In order to be picked up, devices need to be supported by two different mechanisms:
- Android needs to be supplied with a device filter so that it knows what devices to inform
the app about. These are expressed as vendor and device IDs in `src/res/xml/device_filter.xml`.
- The USB driver library also needs to have a mapping between the vendor + device IDs and the
driver to use for communications. Many mappings are already natively supported by the driver
but unknown devices can have manual mappings added via `ProbeTableProvider`.
The [Serial USB Terminal](https://play.google.com/store/apps/details?id=de.kai_morich.serial_usb_terminal)
app in the Google Play Store seems to be a good app for determining both the vendor and
device IDs as well as testing different underlying drivers.
## Testing
When granting permissions to a USB device, the Android platform remembers the user's decision.
In order to test the permission granting logic, re-install the app. This will cause Android
to forget previously granted permissions and will re-trigger the permission acquisition logic.

Wyświetl plik

@ -0,0 +1,43 @@
package com.geeksville.mesh.repository.usb
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import com.geeksville.android.Logging
import com.geeksville.util.exceptionReporter
import javax.inject.Inject
/**
* A helper class to call onChanged when bluetooth is enabled or disabled or when permissions are
* changed.
*/
class UsbBroadcastReceiver @Inject constructor(
private val usbRepository: UsbRepository
) : BroadcastReceiver(), Logging {
// Can be used for registering
internal val intentFilter get() = IntentFilter().apply {
addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
}
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
val deviceName: String = intent.getParcelableExtra<UsbDevice?>(UsbManager.EXTRA_DEVICE)?.deviceName ?: "unknown"
when (intent.action) {
UsbManager.ACTION_USB_DEVICE_DETACHED -> {
debug("USB device '$deviceName' was detached")
usbRepository.refreshState()
}
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
debug("USB device '$deviceName' was attached")
usbRepository.refreshState()
}
UsbManager.EXTRA_PERMISSION_GRANTED -> {
debug("USB device '$deviceName' was granted permission")
usbRepository.refreshState()
}
}
}
}

Wyświetl plik

@ -0,0 +1,77 @@
package com.geeksville.mesh.repository.usb
import android.app.Application
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import com.geeksville.android.Logging
import com.geeksville.mesh.CoroutineDispatchers
import com.hoho.android.usbserial.driver.UsbSerialProber
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
/**
* Repository responsible for maintaining and updating the state of USB connectivity.
*/
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton
class UsbRepository @Inject constructor(
private val application: Application,
private val dispatchers: CoroutineDispatchers,
private val processLifecycle: Lifecycle,
private val usbBroadcastReceiverLazy: dagger.Lazy<UsbBroadcastReceiver>,
private val usbManagerLazy: dagger.Lazy<UsbManager?>,
private val usbSerialProberLazy: dagger.Lazy<UsbSerialProber>
) : Logging {
private val _serialDevices = MutableStateFlow(emptyMap<String, UsbDevice>())
@Suppress("unused") // Retained as public API
val serialDevices = _serialDevices
.asStateFlow()
val serialDevicesWithDrivers = _serialDevices
.mapLatest { serialDevices ->
val serialProber = usbSerialProberLazy.get()
buildMap {
serialDevices.forEach { (k, v) ->
serialProber.probeDevice(v)?.let { driver ->
put(k, driver)
}
}
}
}.stateIn(processLifecycle.coroutineScope, SharingStarted.Eagerly, emptyMap())
@Suppress("unused") // Retained as public API
val serialDevicesWithPermission = _serialDevices
.mapLatest { serialDevices ->
usbManagerLazy.get()?.let { usbManager ->
serialDevices.filterValues { device ->
usbManager.hasPermission(device)
}
} ?: emptyMap()
}.stateIn(processLifecycle.coroutineScope, SharingStarted.Eagerly, emptyMap())
init {
processLifecycle.coroutineScope.launch(dispatchers.default) {
refreshStateInternal()
usbBroadcastReceiverLazy.get().let { receiver ->
application.registerReceiver(receiver, receiver.intentFilter)
}
}
}
fun refreshState() {
processLifecycle.coroutineScope.launch(dispatchers.default) {
refreshStateInternal()
}
}
private suspend fun refreshStateInternal() = withContext(dispatchers.default) {
_serialDevices.emit(usbManagerLazy.get()?.deviceList ?: emptyMap())
}
}

Wyświetl plik

@ -0,0 +1,27 @@
package com.geeksville.mesh.repository.usb
import android.app.Application
import android.content.Context
import android.hardware.usb.UsbManager
import com.hoho.android.usbserial.driver.ProbeTable
import com.hoho.android.usbserial.driver.UsbSerialProber
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface UsbRepositoryModule {
companion object {
@Provides
fun provideUsbManager(application: Application): UsbManager? =
application.getSystemService(Context.USB_SERVICE) as UsbManager?
@Provides
fun provideProbeTable(provider: ProbeTableProvider): ProbeTable = provider.get()
@Provides
fun provideUsbSerialProber(probeTable: ProbeTable): UsbSerialProber = UsbSerialProber(probeTable)
}
}

Wyświetl plik

@ -5,12 +5,12 @@ import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.companion.CompanionDeviceManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import com.geeksville.android.Logging
import com.geeksville.concurrent.handledLaunch
import com.geeksville.mesh.repository.usb.UsbRepository
import com.geeksville.util.anonymize
import com.geeksville.util.exceptionReporter
import com.geeksville.util.ignoreException
@ -85,6 +85,7 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
companion object : Logging, InterfaceFactory('x') {
override fun createInterface(
service: RadioInterfaceService,
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
rest: String
): IRadioInterface = BluetoothInterface(service, rest)
@ -111,7 +112,11 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
/** Return true if this address is still acceptable. For BLE that means, still bonded */
@SuppressLint("NewApi", "MissingPermission")
override fun addressValid(context: Context, rest: String): Boolean {
override fun addressValid(
context: Context,
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
rest: String
): Boolean {
/* val allPaired = if (hasCompanionDeviceApi(context)) {
val deviceManager: CompanionDeviceManager by lazy {
context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager

Wyświetl plik

@ -1,6 +1,7 @@
package com.geeksville.mesh.service
import android.content.Context
import com.geeksville.mesh.repository.usb.UsbRepository
/**
* A base class for the singleton factories that make interfaces. One instance per interface type
@ -16,8 +17,8 @@ abstract class InterfaceFactory(val prefix: Char) {
factories[prefix] = this
}
abstract fun createInterface(service: RadioInterfaceService, rest: String): IRadioInterface
abstract fun createInterface(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, rest: String): Boolean = true
open fun addressValid(context: Context, usbRepository: UsbRepository, rest: String): Boolean = true
}

Wyświetl plik

@ -25,6 +25,7 @@ import com.geeksville.mesh.android.hasBackgroundPermission
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.repository.usb.UsbRepository
import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ProgressNotStarted
import com.geeksville.util.*
import com.google.android.gms.common.api.ApiException
@ -56,6 +57,9 @@ class MeshService : Service(), Logging {
@Inject
lateinit var packetRepository: Lazy<PacketRepository>
@Inject
lateinit var usbRepository: Lazy<UsbRepository>
companion object : Logging {
/// Intents broadcast by MeshService
@ -306,7 +310,7 @@ class MeshService : Service(), Logging {
* tell android not to kill us
*/
private fun startForeground() {
val a = RadioInterfaceService.getBondedDeviceAddress(this)
val a = RadioInterfaceService.getBondedDeviceAddress(this, usbRepository.get())
val wantForeground = a != null && a != "n"
info("Requesting foreground service=$wantForeground")
@ -1337,7 +1341,7 @@ class MeshService : Service(), Logging {
private fun regenMyNodeInfo() {
val myInfo = rawMyNodeInfo
if (myInfo != null) {
val a = RadioInterfaceService.getBondedDeviceAddress(this)
val a = RadioInterfaceService.getBondedDeviceAddress(this, usbRepository.get())
val isBluetoothInterface = a != null && a.startsWith("x")
val nodeNum =

Wyświetl plik

@ -6,6 +6,7 @@ import com.geeksville.android.GeeksvilleApplication
import com.geeksville.android.Logging
import com.geeksville.mesh.*
import com.geeksville.mesh.model.getInitials
import com.geeksville.mesh.repository.usb.UsbRepository
import com.google.protobuf.ByteString
import okhttp3.internal.toHexString
@ -14,10 +15,15 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi
companion object : Logging, InterfaceFactory('m') {
override fun createInterface(
service: RadioInterfaceService,
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
rest: String
): IRadioInterface = MockInterface(service)
override fun addressValid(context: Context, rest: String): Boolean =
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 {

Wyświetl plik

@ -1,11 +1,13 @@
package com.geeksville.mesh.service
import com.geeksville.android.Logging
import com.geeksville.mesh.repository.usb.UsbRepository
class NopInterface : IRadioInterface {
companion object : Logging, InterfaceFactory('n') {
override fun createInterface(
service: RadioInterfaceService,
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
rest: String
): IRadioInterface = NopInterface()

Wyświetl plik

@ -16,12 +16,12 @@ import com.geeksville.android.Logging
import com.geeksville.concurrent.handledLaunch
import com.geeksville.mesh.IRadioInterfaceService
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.usb.UsbRepository
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.collect
import javax.inject.Inject
@ -50,6 +50,9 @@ class RadioInterfaceService : Service(), Logging {
@Inject
lateinit var bluetoothRepository: BluetoothRepository
@Inject
lateinit var usbRepository: UsbRepository
companion object : Logging {
/**
* The RECEIVED_FROMRADIO
@ -95,13 +98,13 @@ class RadioInterfaceService : Service(), Logging {
* and t is an interface specific address (macaddr or a device path)
*/
@SuppressLint("NewApi")
fun getDeviceAddress(context: Context): String? {
fun getDeviceAddress(context: Context, usbRepository: UsbRepository): String? {
// If the user has unpaired our device, treat things as if we don't have one
val prefs = getPrefs(context)
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 (address == null && MockInterface.addressValid(context, ""))
if (address == null && MockInterface.addressValid(context, usbRepository, ""))
address = MockInterface.prefix.toString()
return address
@ -115,15 +118,15 @@ class RadioInterfaceService : Service(), Logging {
* and t is an interface specific address (macaddr or a device path)
*/
@SuppressLint("NewApi")
fun getBondedDeviceAddress(context: Context): String? {
fun getBondedDeviceAddress(context: Context, usbRepository: UsbRepository): String? {
// If the user has unpaired our device, treat things as if we don't have one
val address = getDeviceAddress(context)
val address = getDeviceAddress(context, usbRepository)
/// Interfaces can filter addresses to indicate that address is no longer acceptable
if (address != null) {
val c = address[0]
val rest = address.substring(1)
val isValid = InterfaceFactory.getFactory(c)?.addressValid(context, rest) ?: false
val isValid = InterfaceFactory.getFactory(c)?.addressValid(context, usbRepository, rest) ?: false
if (!isValid)
return null
}
@ -238,7 +241,7 @@ class RadioInterfaceService : Service(), Logging {
if (radioIf !is NopInterface)
warn("Can't start interface - $radioIf is already running")
else {
val address = getBondedDeviceAddress(this)
val address = getBondedDeviceAddress(this, usbRepository)
if (address == null)
warn("No bonded mesh radio, can't start interface")
else {
@ -253,7 +256,7 @@ class RadioInterfaceService : Service(), Logging {
val c = address[0]
val rest = address.substring(1)
radioIf =
InterfaceFactory.getFactory(c)?.createInterface(this, rest) ?: NopInterface()
InterfaceFactory.getFactory(c)?.createInterface(this, usbRepository, rest) ?: NopInterface()
}
}
}
@ -287,7 +290,7 @@ class RadioInterfaceService : Service(), Logging {
*/
@SuppressLint("NewApi")
private fun setBondedDeviceAddress(address: String?): Boolean {
return if (getBondedDeviceAddress(this) == address && isStarted) {
return if (getBondedDeviceAddress(this, usbRepository) == address && isStarted) {
warn("Ignoring setBondedDevice ${address.anonymize}, because we are already using that device")
false
} else {

Wyświetl plik

@ -1,31 +1,30 @@
package com.geeksville.mesh.service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import com.geeksville.android.Logging
import com.geeksville.mesh.android.usbManager
import com.geeksville.util.exceptionReporter
import com.geeksville.mesh.repository.usb.UsbRepository
import com.geeksville.util.ignoreException
import com.hoho.android.usbserial.driver.UsbSerialDriver
import com.hoho.android.usbserial.driver.UsbSerialPort
import com.hoho.android.usbserial.driver.UsbSerialProber
import com.hoho.android.usbserial.util.SerialInputOutputManager
/**
* An interface that assumes we are talking to a meshtastic device via USB serial
*/
class SerialInterface(service: RadioInterfaceService, private val address: String) :
class SerialInterface(
service: RadioInterfaceService,
private val usbRepository: UsbRepository,
private val address: String) :
StreamInterface(service), Logging, SerialInputOutputManager.Listener {
companion object : Logging, InterfaceFactory('s') {
override fun createInterface(
service: RadioInterfaceService,
usbRepository: UsbRepository,
rest: String
): IRadioInterface = SerialInterface(service, rest)
): IRadioInterface = SerialInterface(service, usbRepository, rest)
init {
registerFactory()
@ -36,77 +35,44 @@ class SerialInterface(service: RadioInterfaceService, private val address: Strin
* 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 = true
const val assumePermission = false
fun toInterfaceName(deviceName: String) = "s$deviceName"
fun findDrivers(context: Context): List<UsbSerialDriver> {
val drivers = UsbSerialProber.getDefaultProber().findAllDrivers(context.usbManager)
val devices = drivers.map { it.device }
devices.forEach { d ->
debug("Found serial port ${d.deviceName}")
override fun addressValid(
context: Context,
usbRepository: UsbRepository,
rest: String
): Boolean {
usbRepository.serialDevicesWithDrivers.value.filterValues {
assumePermission || context.usbManager.hasPermission(it.device)
}
return drivers
}
override fun addressValid(context: Context, rest: String): Boolean {
findSerial(context, rest)?.let { d ->
findSerial(usbRepository, rest)?.let { d ->
return assumePermission || context.usbManager.hasPermission(d.device)
}
return false
}
private fun findSerial(context: Context, rest: String): UsbSerialDriver? {
val drivers = findDrivers(context)
return if (drivers.isEmpty())
null
else // Open a connection to the first available driver.
drivers[0] // FIXME, instead we should find by name
private fun findSerial(usbRepository: UsbRepository, rest: String): UsbSerialDriver? {
val deviceMap = usbRepository.serialDevicesWithDrivers.value
deviceMap.forEach { (path, _) ->
debug("Found serial port: $path")
}
return if (deviceMap.containsKey(rest)) {
deviceMap[rest]!!
} else {
deviceMap.map { (_, driver) -> driver }.firstOrNull()
}
}
}
private var uart: UsbSerialDriver? = null
private var ioManager: SerialInputOutputManager? = null
private var usbReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
if (UsbManager.ACTION_USB_DEVICE_DETACHED == intent.action) {
debug("A USB device was detached")
val device: UsbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)!!
if (uart?.device == device)
onDeviceDisconnect(true)
}
if (UsbManager.ACTION_USB_DEVICE_ATTACHED == intent.action) {
debug("attaching USB")
val device: UsbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)!!
if (assumePermission || context.usbManager.hasPermission(device)) {
// reinit the port from scratch and reopen
onDeviceDisconnect(true)
connect()
} else {
warn("We don't have permissions for this USB device")
}
}
}
}
init {
val filter = IntentFilter()
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
service.registerReceiver(usbReceiver, filter)
connect()
}
override fun close() {
service.unregisterReceiver(usbReceiver)
super.close()
}
/** Tell MeshService our device has gone away, but wait for it to come back
*
* @param waitForStopped if true we should wait for the manager to finish - must be false if called from inside the manager callbacks
@ -145,7 +111,7 @@ class SerialInterface(service: RadioInterfaceService, private val address: Strin
override fun connect() {
val manager = service.getSystemService(Context.USB_SERVICE) as UsbManager
val device = findSerial(service, address)
val device = findSerial(usbRepository, address)
if (device != null) {
info("Opening $device")

Wyświetl plik

@ -1,6 +1,7 @@
package com.geeksville.mesh.service
import com.geeksville.android.Logging
import com.geeksville.mesh.repository.usb.UsbRepository
import com.geeksville.util.Exceptions
import java.io.BufferedOutputStream
import java.io.IOException
@ -18,6 +19,7 @@ class TCPInterface(service: RadioInterfaceService, private val address: String)
companion object : Logging, InterfaceFactory('t') {
override fun createInterface(
service: RadioInterfaceService,
usbRepository: UsbRepository, // Temporary until dependency injection transition is completed
rest: String
): IRadioInterface = TCPInterface(service, rest)

Wyświetl plik

@ -36,6 +36,7 @@ import com.geeksville.mesh.android.*
import com.geeksville.mesh.databinding.SettingsFragmentBinding
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.repository.usb.UsbRepository
import com.geeksville.mesh.service.*
import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ACTION_UPDATE_PROGRESS
import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ProgressNotStarted
@ -50,10 +51,10 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.hoho.android.usbserial.driver.UsbSerialDriver
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import java.util.regex.Pattern
import javax.inject.Inject
object SLogging : Logging
@ -108,7 +109,11 @@ private fun requestBonding(
}
}
class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
@HiltViewModel
class BTScanModel @Inject constructor(
private val usbRepository: UsbRepository,
app: Application
) : AndroidViewModel(app), Logging {
private val context: Context get() = getApplication<Application>().applicationContext
@ -243,9 +248,9 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
* returns true if we could start scanning, false otherwise
*/
fun setupScan(): Boolean {
selectedAddress = RadioInterfaceService.getDeviceAddress(context)
selectedAddress = RadioInterfaceService.getDeviceAddress(context, usbRepository)
return if (bluetoothAdapter == null || MockInterface.addressValid(context, "")) {
return if (bluetoothAdapter == null || MockInterface.addressValid(context, usbRepository, "")) {
warn("No bluetooth adapter. Running under emulation?")
val testnodes = listOf(
@ -270,11 +275,11 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
true
} else {
val usbDrivers = SerialInterface.findDrivers(context)
/* model.bluetoothEnabled.value */
if (bluetoothAdapter.bluetoothLeScanner == null && usbDrivers.isEmpty()) {
val serialDevices by lazy {
usbRepository.serialDevicesWithDrivers.value
}
if (bluetoothAdapter.bluetoothLeScanner == null && serialDevices.isEmpty()) {
errorText.value =
context.getString(R.string.requires_bluetooth)
@ -288,10 +293,8 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
// Include a placeholder for "None"
addDevice(DeviceListEntry(context.getString(R.string.none), "n", true))
usbDrivers.forEach { d ->
addDevice(
USBDeviceListEntry(usbManager, d)
)
serialDevices.forEach { (_, d) ->
addDevice(USBDeviceListEntry(usbManager, d))
}
} else {
debug("scan already running")
@ -454,7 +457,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
// FIXME - move this into a standard GUI helper class
private val guiJob = Job()
private val mainScope = CoroutineScope(Dispatchers.Main + guiJob)
@Inject
internal lateinit var usbRepository: UsbRepository
private val hasCompanionDeviceApi: Boolean by lazy {
BluetoothInterface.hasCompanionDeviceApi(requireContext())
@ -823,9 +828,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
// get rid of the warning text once at least one device is paired.
// If we are running on an emulator, always leave this message showing so we can test the worst case layout
val curRadio = RadioInterfaceService.getBondedDeviceAddress(requireContext())
val curRadio = RadioInterfaceService.getBondedDeviceAddress(requireContext(), usbRepository)
if (curRadio != null && !MockInterface.addressValid(requireContext(), "")) {
if (curRadio != null && !MockInterface.addressValid(requireContext(), usbRepository, "")) {
binding.warningNotPaired.visibility = View.GONE
// binding.scanStatusText.text = getString(R.string.current_pair).format(curRadio)
} else if (bluetoothViewModel.enabled.value == true){
@ -1041,7 +1046,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
myActivity.registerReceiver(updateProgressReceiver, updateProgressFilter)
// Keep reminding user BLE is still off
val hasUSB = SerialInterface.findDrivers(myActivity).isNotEmpty()
val hasUSB = usbRepository.serialDevicesWithDrivers.value.isNotEmpty()
if (!hasUSB) {
// Warn user if BLE is disabled
if (scanModel.bluetoothAdapter?.isEnabled != true) {

Wyświetl plik

@ -55,4 +55,5 @@
<usb-device
vendor-id="3368"
product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed -->
<usb-device vendor-id="9114" /> <!-- 0x239A RAK Wireless -->
</resources>