diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index c2dc6c561..e671c3d64 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -5,7 +5,6 @@ import android.bluetooth.BluetoothAdapter import android.content.* import android.content.pm.PackageInfo import android.content.pm.PackageManager -import android.hardware.usb.UsbDevice import android.hardware.usb.UsbManager import android.net.Uri import android.os.Bundle @@ -38,13 +37,10 @@ import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.primaryChannel import com.geeksville.mesh.model.toChannelSet import com.geeksville.mesh.repository.radio.BluetoothInterface -import com.geeksville.mesh.repository.radio.InterfaceId -import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.service.* import com.geeksville.mesh.ui.* import com.geeksville.mesh.ui.map.MapFragment import com.geeksville.mesh.util.Exceptions -import com.geeksville.mesh.util.getParcelableExtraCompat import com.geeksville.mesh.util.LanguageUtils import com.geeksville.mesh.util.getPackageInfoCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -122,9 +118,6 @@ class MainActivity : AppCompatActivity(), Logging { @Inject internal lateinit var serviceRepository: ServiceRepository - @Inject - internal lateinit var radioInterfaceService: RadioInterfaceService - private val bluetoothPermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> if (result.entries.all { it.value }) { @@ -252,9 +245,6 @@ class MainActivity : AppCompatActivity(), Logging { private var requestedChannelUrl: Uri? = null - /** We keep the usb device here, so later we can give it to our service */ - private var usbDevice: UsbDevice? = null - /// Handle any itents that were passed into us private fun handleIntent(intent: Intent) { val appLinkAction = intent.action @@ -272,11 +262,7 @@ class MainActivity : AppCompatActivity(), Logging { } UsbManager.ACTION_USB_DEVICE_ATTACHED -> { - val device: UsbDevice? = intent.getParcelableExtraCompat(UsbManager.EXTRA_DEVICE) - if (device != null) { - debug("Handle USB device attached! $device") - usbDevice = device - } + showSettingsPage() } Intent.ACTION_MAIN -> { @@ -464,13 +450,6 @@ class MainActivity : AppCompatActivity(), Logging { serviceRepository.setMeshService(service) try { - usbDevice?.let { usb -> - debug("Switching to USB radio ${usb.deviceName}") - val address = radioInterfaceService.toInterfaceAddress(InterfaceId.SERIAL, usb.deviceName) - service.setDeviceAddress(address) - usbDevice = null // Only switch once - thereafter it should be stored in settings - } - val connectionState = MeshService.ConnectionState.valueOf(service.connectionState()) @@ -593,8 +572,7 @@ class MainActivity : AppCompatActivity(), Logging { } val bonded = model.bondedAddress != null - if (!bonded && usbDevice == null) // we will handle USB later - showSettingsPage() + if (!bonded) showSettingsPage() } private fun showSettingsPage() { diff --git a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt index 8cec2acbd..e2d5450dd 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -47,6 +47,10 @@ class BTScanModel @Inject constructor( private val bleDevices = MutableLiveData>(listOf()) private val usbDevices = MutableLiveData>(mapOf()) + val isMockInterfaceAddressValid: Boolean by lazy { + radioInterfaceService.isAddressValid(radioInterfaceService.mockInterfaceAddress) + } + init { combine( bluetoothRepository.state, @@ -180,7 +184,7 @@ class BTScanModel @Inject constructor( private fun setupScan(): Boolean { selectedAddress = radioInterfaceService.getDeviceAddress() - return if (radioInterfaceService.isAddressValid(radioInterfaceService.mockInterfaceAddress)) { + return if (isMockInterfaceAddressValid) { warn("Running under emulator/test lab") val testnodes = listOf( diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/InterfaceId.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/InterfaceId.kt index 19eb75449..20f70f695 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/InterfaceId.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/InterfaceId.kt @@ -8,11 +8,12 @@ enum class InterfaceId(val id: Char) { MOCK('m'), NOP('n'), SERIAL('s'), - TCP('t'); + TCP('t'), + ; companion object { fun forIdChar(id: Char): InterfaceId? { - return values().firstOrNull { it.id == id } + return entries.firstOrNull { it.id == id } } } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt index e0b9efa56..540673d2d 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt @@ -1,6 +1,5 @@ package com.geeksville.mesh.repository.radio -import android.annotation.SuppressLint import android.app.Application import android.content.SharedPreferences import androidx.core.content.edit @@ -16,7 +15,10 @@ import com.geeksville.mesh.repository.nsd.NsdRepository import com.geeksville.mesh.util.anonymize import com.geeksville.mesh.util.ignoreException import com.geeksville.mesh.util.toRemoteExceptions -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -46,7 +48,6 @@ class RadioInterfaceService @Inject constructor( private val processLifecycle: Lifecycle, @RadioRepositoryQualifier private val prefs: SharedPreferences, private val interfaceFactory: InterfaceFactory, - private val mockInterfaceSpec: MockInterfaceSpec ) : Logging { private val _connectionState = MutableStateFlow(RadioServiceConnectionState()) @@ -132,8 +133,9 @@ class RadioInterfaceService @Inject constructor( 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 && mockInterfaceSpec.addressValid("")) - address = "${InterfaceId.MOCK.id}" + if (address == null && isAddressValid(mockInterfaceAddress)) { + address = mockInterfaceAddress + } return address } @@ -145,7 +147,6 @@ class RadioInterfaceService @Inject constructor( * where a is either x for bluetooth or s for serial * and t is an interface specific address (macaddr or a device path) */ - @SuppressLint("NewApi") fun getBondedDeviceAddress(): String? { // If the user has unpaired our device, treat things as if we don't have one val address = getDeviceAddress() @@ -248,7 +249,6 @@ class RadioInterfaceService @Inject constructor( * * @return true if the device changed, false if no change */ - @SuppressLint("NewApi") private fun setBondedDeviceAddress(address: String?): Boolean { return if (getBondedDeviceAddress() == address && isStarted) { warn("Ignoring setBondedDevice ${address.anonymize}, because we are already using that device") diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt index 516a66afe..54fbf3dca 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt @@ -34,7 +34,7 @@ class SerialInterface @AssistedInject constructor( errormsg("Can't find device") } else { info("Opening $device") - val onConnect: () -> Unit = { super.connect() } + val onConnect: () -> Unit = { super.connect() } usbRepository.createSerialConnection(device, object : SerialConnectionListener { override fun onMissingPermission() { errormsg("Need permissions for port") diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index bc5888ef5..ce784aae3 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -8,7 +8,6 @@ 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 android.os.Bundle import android.os.Handler @@ -41,7 +40,6 @@ import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.getInitials import com.geeksville.mesh.repository.location.LocationRepository -import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.service.SoftwareUpdateService import com.geeksville.mesh.util.PendingIntentCompat @@ -66,9 +64,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private val bluetoothViewModel: BluetoothViewModel by activityViewModels() private val model: UIViewModel by activityViewModels() - @Inject - internal lateinit var radioInterfaceServiceLazy: dagger.Lazy - @Inject internal lateinit var locationRepository: LocationRepository @@ -502,8 +497,7 @@ 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 val curRadio = scanModel.selectedAddress - val radioInterfaceService = radioInterfaceServiceLazy.get() - if (curRadio != null && !radioInterfaceService.isAddressValid(radioInterfaceService.mockInterfaceAddress)) { + if (curRadio != null && !scanModel.isMockInterfaceAddressValid) { binding.warningNotPaired.visibility = View.GONE } else if (bluetoothViewModel.enabled.value == true) { binding.warningNotPaired.visibility = View.VISIBLE @@ -574,22 +568,13 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { val usbReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - if (BTScanModel.ACTION_USB_PERMISSION == intent.action) { + if (BTScanModel.ACTION_USB_PERMISSION != intent.action) return - val device: UsbDevice? = - intent.getParcelableExtraCompat(UsbManager.EXTRA_DEVICE) - val deviceName: String = device?.deviceName ?: "unknown" - - if (intent.getBooleanExtra( - UsbManager.EXTRA_PERMISSION_GRANTED, - false - ) - ) { - info("User approved USB access") - changeDeviceAddress(it.fullAddress) - } else { - errormsg("USB permission denied for device $deviceName") - } + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { + info("User approved USB access") + changeDeviceAddress(it.fullAddress) + } else { + errormsg("USB permission denied for device ${it.address}") } // We don't need to stay registered requireActivity().unregisterReceiver(this) @@ -600,7 +585,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { activity, 0, Intent(BTScanModel.ACTION_USB_PERMISSION), - PendingIntentCompat.FLAG_IMMUTABLE + PendingIntentCompat.FLAG_MUTABLE ) val filter = IntentFilter(BTScanModel.ACTION_USB_PERMISSION) requireActivity().registerReceiver(usbReceiver, filter) @@ -622,12 +607,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // We need this receiver to get informed when the bond attempt finished val bondChangedReceiver = object : BroadcastReceiver() { - override fun onReceive( - context: Context, - intent: Intent - ) = exceptionReporter { - val state = - intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1) + override fun onReceive(context: Context, intent: Intent) = exceptionReporter { + val state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1) debug("Received bond state changed $state") if (state != BluetoothDevice.BOND_BONDING) {