From 2de49c143b9254203eb37b403ffe0875d5faabc6 Mon Sep 17 00:00:00 2001 From: Davis Date: Sun, 25 Feb 2024 03:38:51 -0700 Subject: [PATCH] Remove UI for firmware update (button and progress) and accompanying logic (#870) Use non-deprecated method for checking IP address format --- .../geeksville/mesh/service/MeshService.kt | 25 +- .../mesh/service/SoftwareUpdateService.kt | 389 +----------------- .../geeksville/mesh/ui/SettingsFragment.kt | 141 ++----- app/src/main/res/layout/settings_fragment.xml | 28 +- 4 files changed, 40 insertions(+), 543 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 2b3dec165..4895fa872 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -204,7 +204,9 @@ class MeshService : Service(), Logging { debug("Sending to radio ${built.toPIIString()}") val b = built.toByteArray() - if (SoftwareUpdateService.isUpdating) throw IsUpdatingException() + if (false) { // TODO check if radio is updating + throw IsUpdatingException() + } radioInterfaceService.sendToRadio(b) changeStatus(p.packet.id, MessageStatus.ENROUTE) @@ -1133,13 +1135,7 @@ class MeshService : Service(), Logging { // Do our startup init try { connectTimeMsec = System.currentTimeMillis() - SoftwareUpdateService.sendProgress( - this, - SoftwareUpdateService.ProgressNotStarted, - true - ) // Kinda crufty way of reiniting software update startConfig() - } catch (ex: InvalidProtocolBufferException) { errormsg( "Invalid protocol buffer sent by device - update device software and try again", @@ -1342,10 +1338,7 @@ class MeshService : Service(), Logging { hwModelStr, firmwareVersion, firmwareUpdateFilename?.appLoad != null && firmwareUpdateFilename?.littlefs != null, - isBluetoothInterface && SoftwareUpdateService.shouldUpdate( - this@MeshService, - DeviceVersion(firmwareVersion) - ), + shouldUpdate = false, // TODO add check after re-implementing firmware updates currentPacketId and 0xffffffffL, 5 * 60 * 1000, // constants from current device code minAppVersion, @@ -1632,10 +1625,8 @@ class MeshService : Service(), Logging { private fun setFirmwareUpdateFilename(model: String?) { firmwareUpdateFilename = try { if (model != null) - SoftwareUpdateService.getUpdateFilename( - this, - model - ) + // TODO reimplement this after we have a new firmware update mechanism + null else null } catch (ex: Exception) { @@ -1664,7 +1655,7 @@ class MeshService : Service(), Logging { updateJob = serviceScope.handledLaunch { exceptionReporter { debug("Starting firmware update coroutine") - SoftwareUpdateService.doUpdate(this@MeshService, safe, filename) + // TODO perform update with new firmware update mechanism } } } @@ -1691,7 +1682,7 @@ class MeshService : Service(), Logging { clientPackages[receiverName] = packageName } - override fun getUpdateStatus(): Int = SoftwareUpdateService.progress + override fun getUpdateStatus(): Int = 0 // TODO reimplement this after we have a new firmware update mechanism override fun startFirmwareUpdate() = toRemoteExceptions { doFirmwareUpdate() diff --git a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt index 3e76e0726..16ac63e59 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -1,18 +1,6 @@ package com.geeksville.mesh.service -import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothGattCharacteristic -import android.bluetooth.BluetoothManager -import android.content.Context -import android.content.Intent -import androidx.core.app.JobIntentService -import com.geeksville.mesh.android.Logging -import com.geeksville.mesh.MainActivity -import com.geeksville.mesh.R -import com.geeksville.mesh.model.DeviceVersion -import com.geeksville.mesh.util.exceptionReporter -import java.util.* -import java.util.zip.CRC32 /** * Some misformatted ESP32s have problems @@ -69,379 +57,4 @@ fun toNetworkByteArray(value: Int, formatType: Int): ByteArray { return mValue } - -data class UpdateFilenames(val appLoad: String?, val littlefs: String?) - -/** - * typical flow - * - * startScan - * startUpdate - * - * stopScan - * - * FIXME - if we don't find a device stop our scan - * FIXME - broadcast when we found devices, made progress sending blocks or when the update is complete - * FIXME - make the user decide to start an update on a particular device - */ -class SoftwareUpdateService : JobIntentService(), Logging { - - - private val bluetoothAdapter: BluetoothAdapter by lazy(LazyThreadSafetyMode.NONE) { - val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager - bluetoothManager.adapter!! - } - - - private fun startUpdate(macaddr: String) { - info("starting update to $macaddr") - - val device = bluetoothAdapter.getRemoteDevice(macaddr) - - val sync = - SafeBluetooth( - this@SoftwareUpdateService, - device - ) - - sync.connect() - sync.use { _ -> - // we begin by setting our MTU size as high as it can go - sync.requestMtu(512) - - sync.discoverServices() // Get our services - - val updateFilename = getUpdateFilename(this, sync) - if (updateFilename != null) { - doUpdate(this, sync, updateFilename) - } else - warn("Device is already up-to-date no update needed.") - } - } - - - override fun onHandleWork(intent: Intent) { - // We have received work to do. The system or framework is already -// holding a wake lock for us at this point, so we can just go. - - // Report failures but do not crash the app - exceptionReporter { - debug("Executing work: $intent") - when (intent.action) { - ACTION_START_UPDATE -> { - val addr = intent.getStringExtra(EXTRA_MACADDR) - ?: throw Exception("EXTRA_MACADDR not specified") - startUpdate(addr) // FIXME, pass in as an intent arg instead - } - else -> TODO("Unhandled case") - } - } - } - - companion object : Logging { - /** - * Unique job ID for this service. Must be the same for all work. - */ - private const val JOB_ID = 1000 - - fun startUpdateIntent(macAddress: String): Intent { - val i = Intent(ACTION_START_UPDATE) - i.putExtra(EXTRA_MACADDR, macAddress) - - return i - } - - const val ACTION_START_UPDATE = "$prefix.START_UPDATE" - - const val ACTION_UPDATE_PROGRESS = "$prefix.UPDATE_PROGRESS" - - const val EXTRA_MACADDR = "macaddr" - - private const val SCAN_PERIOD: Long = 10000 - - private val TAG = - MainActivity::class.java.simpleName // FIXME - use my logging class instead - - private val SW_UPDATE_UUID = UUID.fromString("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30") - private val SW_UPDATE_TOTALSIZE_CHARACTER = - UUID.fromString("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e") // write|read total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) - private val SW_UPDATE_DATA_CHARACTER = - UUID.fromString("e272ebac-d463-4b98-bc84-5cc1a39ee517") // write data, variable sized, recommended 512 bytes, write one for each block of file - private val SW_UPDATE_CRC32_CHARACTER = - UUID.fromString("4826129c-c22a-43a3-b066-ce8f0d5bacc6") // write crc32, write last - writing this will complete the OTA operation, now you can read result - private val SW_UPDATE_RESULT_CHARACTER = - UUID.fromString("5e134862-7411-4424-ac4a-210937432c77") // read|notify result code, readable but will notify when the OTA operation completes - private val SW_UPDATE_REGION_CHARACTER = - UUID.fromString("5e134862-7411-4424-ac4a-210937432c67") // write - used to set the region we are setting (appload vs littlefs) - - private val SW_VERSION_CHARACTER = longBLEUUID("2a28") - private val MANUFACTURE_CHARACTER = longBLEUUID("2a29") - private val HW_VERSION_CHARACTER = longBLEUUID("2a27") - - const val ProgressSuccess = -1 - const val ProgressUpdateFailed = -2 - const val ProgressBleException = -3 - const val ProgressNotStarted = -4 - - /** - * % progress through the update - */ - var progress = ProgressNotStarted - - /** - * Convenience method for enqueuing work in to this service. - */ - fun enqueueWork(context: Context, work: Intent) { - enqueueWork( - context, - SoftwareUpdateService::class.java, - JOB_ID, work - ) - } - - - /** - * true if we are busy with an update right now - */ - val isUpdating get() = progress >= 0 - - /** - * Update our progress indication for GUIs - * - * @param isAppload if false, we don't report failure indications (because we consider littlefs non critical for now). But do report to analytics - */ - fun sendProgress(context: Context, p: Int, isAppload: Boolean) { - if (!isAppload && p < 0) - errormsg("Error while writing littlefs $p") // treat errors writing littlefs as non fatal for now (user partition probably missized and most people don't need it) - else - if (progress != p) { - progress = p - - val intent = Intent(ACTION_UPDATE_PROGRESS).putExtra( - EXTRA_PROGRESS, - p - ) - context.sendBroadcast(intent) - } - } - - /** Return true if we thing the firmwarte shoulde be updated - * - * @param swVer the version of the software running on the target - */ - fun shouldUpdate( - context: Context, - deviceVersion: DeviceVersion - ): Boolean = try { - val curVer = DeviceVersion(context.getString(R.string.cur_firmware_version)) - val minVer = - DeviceVersion("0.7.8") // The oldest device version with a working software update service - - ((curVer > deviceVersion) && (deviceVersion >= minVer)) - } catch (ex: Exception) { - errormsg("Error finding swupdate info", ex) - false // If we fail parsing our update info - } - - /** Return a Pair of appload filename, littlefs filename this device needs to use as an update (or null if no update needed) - */ - fun getUpdateFilename( - context: Context, - mfg: String - ): UpdateFilenames { - val curVer = context.getString(R.string.cur_firmware_version) - - // Check to see if the file exists (some builds might not include update files for size reasons) - val firmwareFiles = context.assets.list("firmware") ?: arrayOf() - - val appLoad = "firmware-$mfg-$curVer.bin" - val littlefs = "littlefs-$curVer.bin" - - return UpdateFilenames( - if (firmwareFiles.contains(appLoad)) - "firmware/$appLoad" - else - null, - if (firmwareFiles.contains(littlefs)) - "firmware/$littlefs" - else - null - ) - } - - /** Return the filename this device needs to use as an update (or null if no update needed) - * No longer used, because we get update info inband from our radio API - */ - fun getUpdateFilename(context: Context, sync: SafeBluetooth): UpdateFilenames? { - val service = sync.gatt!!.services.find { it.uuid == SW_UPDATE_UUID }!! - - //val hwVerDesc = service.getCharacteristic(HW_VERSION_CHARACTER) - val mfgDesc = service.getCharacteristic(MANUFACTURE_CHARACTER) - //val swVerDesc = service.getCharacteristic(SW_VERSION_CHARACTER) - - // looks like HELTEC - val mfg = sync.readCharacteristic(mfgDesc).getStringValue(0) - - return getUpdateFilename(context, mfg) - } - - /** - * A public function so that if you have your own SafeBluetooth connection already open - * you can use it for the software update. - */ - fun doUpdate(context: Context, sync: SafeBluetooth, assets: UpdateFilenames) { - // calculate total firmware size (littlefs + appLoad) - var totalFirmwareSize = 0 - if (assets.appLoad != null && assets.littlefs != null) { - totalFirmwareSize += context.assets.open(assets.appLoad).available() - totalFirmwareSize += context.assets.open(assets.littlefs).available() - } - // we must attempt littlefs first, because if we update the appload the device will reboot afterwards - try { - assets.littlefs?.let { doUpdate(context, sync, it, FLASH_REGION_LITTLEFS, totalFirmwareSize) } - } catch (_: BLECharacteristicNotFoundException) { - // If we can't update littlefs (because not supported by target), do not fail - errormsg("Ignoring failure to update littlefs on old appload") - } catch (_: DeviceRejectedException) { - // the spi filesystem of this device is malformatted, fail silently because most users don't need the web server - errormsg("Device rejected invalid littlefs partition") - } - - assets.appLoad?.let { doUpdate(context, sync, it, FLASH_REGION_APPLOAD, totalFirmwareSize) } - sendProgress(context, ProgressSuccess, true) - } - - // writable region codes in the ESP32 update code - private val FLASH_REGION_APPLOAD = 0 - private val FLASH_REGION_LITTLEFS = 100 - - /** - * A public function so that if you have your own SafeBluetooth connection already open - * you can use it for the software update. - */ - private fun doUpdate( - context: Context, - sync: SafeBluetooth, - assetName: String, - flashRegion: Int = FLASH_REGION_APPLOAD, - totalFirmwareSize: Int = 0 - ) { - val isAppload = flashRegion == FLASH_REGION_APPLOAD - - try { - val g = sync.gatt!! - val service = g.services.find { it.uuid == SW_UPDATE_UUID } - ?: throw BLEException("Couldn't find update service") - - /** - * Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null - */ - fun getCharacteristic(uuid: UUID) = - service.getCharacteristic(uuid) - ?: throw BLECharacteristicNotFoundException(uuid) - - info("Starting firmware update for $assetName, flash region $flashRegion") - - sendProgress(context, 0, isAppload) - val totalSizeDesc = getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER) - val dataDesc = getCharacteristic(SW_UPDATE_DATA_CHARACTER) - val crc32Desc = getCharacteristic(SW_UPDATE_CRC32_CHARACTER) - val updateResultDesc = getCharacteristic(SW_UPDATE_RESULT_CHARACTER) - - /// Try to set the destination region for programming (littlefs vs appload etc) - /// Old apploads don't have this feature, but we only fail if the user was trying to set a - /// littlefs - otherwise we assume appload. - try { - val updateRegionDesc = getCharacteristic(SW_UPDATE_REGION_CHARACTER) - sync.writeCharacteristic( - updateRegionDesc, - toNetworkByteArray(flashRegion, BluetoothGattCharacteristic.FORMAT_UINT8) - ) - } catch (ex: BLECharacteristicNotFoundException) { - errormsg("Can't set flash programming region (old appload?") - if (flashRegion != FLASH_REGION_APPLOAD) { - throw ex - } - warn("Ignoring setting appload flashRegion") - } - - context.assets.open(assetName).use { firmwareStream -> - val firmwareCrc = CRC32() - var firmwareNumSent = 0 - val firmwareSize = firmwareStream.available() - - // Start the update by writing the # of bytes in the image - sync.writeCharacteristic( - totalSizeDesc, - toNetworkByteArray(firmwareSize, BluetoothGattCharacteristic.FORMAT_UINT32) - ) - - // Our write completed, queue up a readback - val totalSizeReadback = sync.readCharacteristic(totalSizeDesc) - .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0) - if (totalSizeReadback == 0) - throw DeviceRejectedException() - - // Send all the blocks - var oldProgress = -1 // used to limit # of log spam - while (firmwareNumSent < firmwareSize) { - // If we are doing the littlefs partition, we limit progress to a max of maxProgress - // when updating the appload partition, progress from (100 - maxProgress) to 100% - // maxProgress = littlefs% = 100% - appLoad%; (int * 10 + 5) / 10 used for rounding - val maxProgress = ((firmwareSize * 1000 / totalFirmwareSize) + 5) / 10 - val minProgress = if (flashRegion != FLASH_REGION_APPLOAD) - 0 else (100 - maxProgress) - sendProgress( - context, - minProgress + firmwareNumSent * maxProgress / firmwareSize, - isAppload - ) - if (progress != oldProgress) { - debug("sending block ${progress}%") - oldProgress = progress - } - var blockSize = 512 - 3 // Max size MTU excluding framing - - if (blockSize > firmwareStream.available()) - blockSize = firmwareStream.available() - val buffer = ByteArray(blockSize) - - // slightly expensive to keep reallocing this buffer, but whatever - logAssert(firmwareStream.read(buffer) == blockSize) - firmwareCrc.update(buffer) - - sync.writeCharacteristic(dataDesc, buffer) - firmwareNumSent += blockSize - } - - try { - // We have finished sending all our blocks, so post the CRC so our state machine can advance - val c = firmwareCrc.value - info("Sent all blocks, crc is $c") - sync.writeCharacteristic( - crc32Desc, - toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32) - ) - - // we just read the update result if !0 we have an error - val updateResult = - sync.readCharacteristic(updateResultDesc) - .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) - if (updateResult != 0) { - sendProgress(context, ProgressUpdateFailed, isAppload) - throw Exception("Device update failed, reason=$updateResult") - } - - // Device will now reboot - } catch (ex: BLEException) { - // We might get SyncContinuation timeout on the final write, assume the device simply rebooted to run the new load and we missed it - errormsg("Assuming successful update", ex) - } - } - } catch (ex: BLEException) { - sendProgress(context, ProgressBleException, isAppload) - throw ex // Unexpected BLE exception - } - } - } -} +data class UpdateFilenames(val appLoad: String?, val littlefs: String?) \ No newline at end of file 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 c3aab5e09..e619dcafb 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -1,12 +1,11 @@ package com.geeksville.mesh.ui -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter +import android.net.InetAddresses +import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper +import android.text.Editable import android.util.Patterns import android.view.LayoutInflater import android.view.View @@ -21,7 +20,6 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.asLiveData import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.R -import com.geeksville.mesh.analytics.DataPair import com.geeksville.mesh.ModuleConfigProtos import com.geeksville.mesh.android.* import com.geeksville.mesh.databinding.SettingsFragmentBinding @@ -31,7 +29,6 @@ import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.getInitials import com.geeksville.mesh.repository.location.LocationRepository import com.geeksville.mesh.service.MeshService -import com.geeksville.mesh.service.SoftwareUpdateService import com.geeksville.mesh.util.exceptionToSnackbar import com.geeksville.mesh.util.getAssociationResult import com.geeksville.mesh.util.onEditorAction @@ -56,25 +53,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private val hasGps by lazy { requireContext().hasGps() } private val hasCompanionDeviceApi by lazy { requireContext().hasCompanionDeviceApi() } - private fun doFirmwareUpdate() { - model.meshService?.let { service -> - - debug("User started firmware update") - GeeksvilleApplication.analytics.track( - "firmware_update", - DataPair("content_type", "start") - ) - binding.updateFirmwareButton.isEnabled = false // Disable until things complete - binding.updateProgressBar.visibility = View.VISIBLE - binding.updateProgressBar.progress = 0 // start from scratch - - exceptionToSnackbar(requireView()) { - // We rely on our broadcast receiver to show progress as this progresses - service.startFirmwareUpdate() - } - } - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -83,56 +61,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { return binding.root } - /// Set the correct update button configuration based on current progress - private fun refreshUpdateButton(enable: Boolean) { - debug("Reiniting the update button") - val info = model.myNodeInfo.value - val service = model.meshService - if (model.isConnected() && info != null && info.shouldUpdate && info.couldUpdate && service != null) { - binding.updateFirmwareButton.visibility = View.VISIBLE - binding.updateFirmwareButton.text = - getString(R.string.update_to).format(getString(R.string.short_firmware_version)) - - val progress = service.updateStatus - - binding.updateFirmwareButton.isEnabled = enable && - (progress < 0) // if currently doing an upgrade disable button - - if (progress >= 0) { - binding.updateProgressBar.progress = progress // update partial progress - binding.scanStatusText.setText(R.string.updating_firmware) - binding.updateProgressBar.visibility = View.VISIBLE - } else - when (progress) { - SoftwareUpdateService.ProgressSuccess -> { - GeeksvilleApplication.analytics.track( - "firmware_update", - DataPair("content_type", "success") - ) - binding.scanStatusText.setText(R.string.update_successful) - binding.updateProgressBar.visibility = View.GONE - } - SoftwareUpdateService.ProgressNotStarted -> { - // Do nothing - because we don't want to overwrite the status text in this case - binding.updateProgressBar.visibility = View.GONE - } - else -> { - GeeksvilleApplication.analytics.track( - "firmware_update", - DataPair("content_type", "failure") - ) - binding.scanStatusText.setText(R.string.update_failed) - binding.updateProgressBar.visibility = View.VISIBLE - } - } - binding.updateProgressBar.isEnabled = false - - } else { - binding.updateFirmwareButton.visibility = View.GONE - binding.updateProgressBar.visibility = View.GONE - } - } - /** * Pull the latest device info from the model and into the GUI */ @@ -168,9 +96,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { spinner.onItemSelectedListener = regionSpinnerListener spinner.isEnabled = !model.isManaged - // If actively connected possibly let the user update firmware - refreshUpdateButton(isConnected) - // Update the status string (highest priority messages first) val info = model.myNodeInfo.value when (connectionState) { @@ -313,17 +238,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - binding.updateFirmwareButton.setOnClickListener { - MaterialAlertDialogBuilder(requireContext()) - .setMessage("${getString(R.string.update_firmware)}?") - .setNeutralButton(R.string.cancel) { _, _ -> - } - .setPositiveButton(getString(R.string.okay)) { _, _ -> - doFirmwareUpdate() - } - .show() - } - binding.usernameEditText.onEditorAction(EditorInfo.IME_ACTION_DONE) { debug("received IME_ACTION_DONE") val n = binding.usernameEditText.text.toString().trim() @@ -390,6 +304,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { binding.reportBugButton.setOnClickListener(::showReportBugDialog) } + @Suppress("UNUSED_PARAMETER") private fun showReportBugDialog(view: View) { MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.report_a_bug) @@ -420,19 +335,19 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } private fun addManualDeviceButton() { - val b = binding.radioButtonManual - val e = binding.editManualAddress + val deviceSelectIPAddress = binding.radioButtonManual + val inputIPAddress = binding.editManualAddress - b.isEnabled = false - - binding.deviceRadioGroup.addView(b) - - b.setOnClickListener { - b.isChecked = scanModel.onSelected(BTScanModel.DeviceListEntry("", "t" + e.text, true)) + deviceSelectIPAddress.isEnabled = false + deviceSelectIPAddress.setOnClickListener { + deviceSelectIPAddress.isChecked = scanModel.onSelected(BTScanModel.DeviceListEntry("", "t" + inputIPAddress.text, true)) } - binding.deviceRadioGroup.addView(e) - e.doAfterTextChanged { - b.isEnabled = Patterns.IP_ADDRESS.matcher(e.text).matches() + + binding.deviceRadioGroup.addView(deviceSelectIPAddress) + binding.deviceRadioGroup.addView(inputIPAddress) + + inputIPAddress.doAfterTextChanged { + deviceSelectIPAddress.isEnabled = inputIPAddress.text.isIPAddress() } } @@ -545,25 +460,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - private val updateProgressFilter = IntentFilter(SoftwareUpdateService.ACTION_UPDATE_PROGRESS) - - private val updateProgressReceiver: BroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - refreshUpdateButton(true) - } - } - - override fun onPause() { - super.onPause() - - requireActivity().unregisterReceiver(updateProgressReceiver) - } - override fun onResume() { super.onResume() - requireActivity().registerReceiver(updateProgressReceiver, updateProgressFilter) - // Warn user if BLE device is selected but BLE disabled if (scanModel.selectedBluetooth) checkBTEnabled() @@ -580,4 +479,14 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { companion object { const val SCAN_PERIOD: Long = 10000 // Stops scanning after 10 seconds } + + private fun Editable.isIPAddress(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + InetAddresses.isNumericAddress(this.toString()) + } else { + @Suppress("DEPRECATION") + Patterns.IP_ADDRESS.matcher(this).matches() + } + } + } diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 21e3b510e..31f080f9c 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -74,9 +74,10 @@ android:layout_marginTop="16dp" android:layout_marginEnd="8dp" android:text="@string/looking_for_meshtastic_devices" - app:layout_constraintEnd_toStartOf="@+id/updateFirmwareButton" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/nodeSettings" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/nodeSettings" + /> + android:visibility="visible" + android:importantForAutofill="no" + /> @@ -137,25 +140,6 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@+id/reportBugButton" /> - - - -