mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
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.
This commit is contained in:
parent
26b6081e9c
commit
dd41527bbc
17 changed files with 293 additions and 102 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue