From 8a065c5b885f83acf90e5922158938886d8b394b Mon Sep 17 00:00:00 2001 From: Vadim Furman Date: Fri, 12 Feb 2021 19:07:14 -0800 Subject: [PATCH 1/4] Per-channel minimum broadcast period enforcement --- .../geeksville/mesh/model/ChannelOption.kt | 10 ++++----- .../geeksville/mesh/ui/SettingsFragment.kt | 21 +++++++++++++------ app/src/main/res/values/strings.xml | 1 + 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/ChannelOption.kt b/app/src/main/java/com/geeksville/mesh/model/ChannelOption.kt index 2ceae83e..0f0e929e 100644 --- a/app/src/main/java/com/geeksville/mesh/model/ChannelOption.kt +++ b/app/src/main/java/com/geeksville/mesh/model/ChannelOption.kt @@ -3,11 +3,11 @@ package com.geeksville.mesh.model import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.R -enum class ChannelOption(val modemConfig: MeshProtos.ChannelSettings.ModemConfig, val configRes: Int) { - SHORT(MeshProtos.ChannelSettings.ModemConfig.Bw500Cr45Sf128, R.string.modem_config_short), - MEDIUM(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128, R.string.modem_config_medium), - LONG(MeshProtos.ChannelSettings.ModemConfig.Bw31_25Cr48Sf512, R.string.modem_config_long), - VERY_LONG(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr48Sf4096, R.string.modem_config_very_long); +enum class ChannelOption(val modemConfig: MeshProtos.ChannelSettings.ModemConfig, val configRes: Int, val minBroadcastPeriodSecs: Int) { + SHORT(MeshProtos.ChannelSettings.ModemConfig.Bw500Cr45Sf128, R.string.modem_config_short, 3), + MEDIUM(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128, R.string.modem_config_medium, 12), + LONG(MeshProtos.ChannelSettings.ModemConfig.Bw31_25Cr48Sf512, R.string.modem_config_long, 240), + VERY_LONG(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr48Sf4096, R.string.modem_config_very_long, 375); companion object { fun fromConfig(modemConfig: MeshProtos.ChannelSettings.ModemConfig?): ChannelOption? { 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 a582d3c5..5d9ed961 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -30,11 +30,11 @@ import com.geeksville.android.Logging import com.geeksville.android.hideKeyboard import com.geeksville.android.isGooglePlayAvailable import com.geeksville.mesh.MainActivity -import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.R import com.geeksville.mesh.android.bluetoothManager import com.geeksville.mesh.android.usbManager import com.geeksville.mesh.databinding.SettingsFragmentBinding +import com.geeksville.mesh.model.ChannelOption import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.BluetoothInterface import com.geeksville.mesh.service.MeshService @@ -620,13 +620,22 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } binding.positionBroadcastPeriodEditText.on(EditorInfo.IME_ACTION_DONE) { - val str = binding.positionBroadcastPeriodEditText.text.toString() - val n = str.toIntOrNull() - if (n != null && n <= MAX_INT_DEVICE && n >= 0) { - model.positionBroadcastSecs = n + val textEdit = binding.positionBroadcastPeriodEditText + val n = textEdit.text.toString().toIntOrNull() + val minBroadcastPeriodSecs = + ChannelOption.fromConfig(model.radioConfig.value?.channelSettings?.modemConfig)?.minBroadcastPeriodSecs + ?: 9000 + info("edit broadcast $n min $minBroadcastPeriodSecs") + if (n != null && n >= 0 && n >= minBroadcastPeriodSecs) { + model.positionBroadcastSecs = n } else { - binding.scanStatusText.text = "Bad value: $str" + // restore the value in the edit field + textEdit.setText(model.positionBroadcastSecs.toString()) + val errorText = if (n == null || n <= 0) "Bad value: ${textEdit.text.toString()}" else + getString(R.string.broadcast_period_too_small).format(minBroadcastPeriodSecs) + Toast.makeText(context, errorText, Toast.LENGTH_LONG).show() } + requireActivity().hideKeyboard() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de41846f..af0fa2bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -86,4 +86,5 @@ Broadcast position period (in seconds), 0 - disable Device sleep period (in seconds) Notifications about messages + Minimum broadcast period for this channel is %d From 380ca1017fc09fafca77acf1cd76edc400991868 Mon Sep 17 00:00:00 2001 From: Vadim Furman Date: Sat, 13 Feb 2021 18:53:59 -0800 Subject: [PATCH 2/4] Use snackbar --- .../main/java/com/geeksville/mesh/ui/SettingsFragment.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 5d9ed961..fe0ad40f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -49,6 +49,7 @@ import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationSettingsRequest import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.snackbar.Snackbar import com.hoho.android.usbserial.driver.UsbSerialDriver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -626,14 +627,14 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { ChannelOption.fromConfig(model.radioConfig.value?.channelSettings?.modemConfig)?.minBroadcastPeriodSecs ?: 9000 info("edit broadcast $n min $minBroadcastPeriodSecs") - if (n != null && n >= 0 && n >= minBroadcastPeriodSecs) { - model.positionBroadcastSecs = n + if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) { + model.positionBroadcastSecs = n } else { // restore the value in the edit field textEdit.setText(model.positionBroadcastSecs.toString()) - val errorText = if (n == null || n <= 0) "Bad value: ${textEdit.text.toString()}" else + val errorText = if (n == null || n < 0 || n >= MAX_INT_DEVICE) "Bad value: ${textEdit.text.toString()}" else getString(R.string.broadcast_period_too_small).format(minBroadcastPeriodSecs) - Toast.makeText(context, errorText, Toast.LENGTH_LONG).show() + Snackbar.make(requireView(), errorText, Snackbar.LENGTH_LONG).show() } requireActivity().hideKeyboard() From 757c3867f202bd6b25d7b2dc97d1190005249ef4 Mon Sep 17 00:00:00 2001 From: Vadim Furman Date: Sat, 13 Feb 2021 21:35:57 -0800 Subject: [PATCH 3/4] Moved broadcast and ls_sleep in advanced settings --- .../java/com/geeksville/mesh/MainActivity.kt | 10 +++ .../mesh/ui/AdvancedSettingsFragment.kt | 82 +++++++++++++++++++ .../geeksville/mesh/ui/SettingsFragment.kt | 43 +--------- app/src/main/res/layout/advanced_settings.xml | 50 +++++++++++ app/src/main/res/layout/settings_fragment.xml | 42 ---------- app/src/main/res/menu/menu_main.xml | 4 + 6 files changed, 147 insertions(+), 84 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt create mode 100644 app/src/main/res/layout/advanced_settings.xml diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 4481f768..c960a6a9 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -52,6 +52,7 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.tasks.Task import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayoutMediator import com.google.protobuf.InvalidProtocolBufferException import com.vorlonsoft.android.rate.AppRate @@ -978,6 +979,15 @@ class MainActivity : AppCompatActivity(), Logging, handler.removeCallbacksAndMessages(null) return true } + R.id.advanced_settings -> { + val fragmentManager: FragmentManager = supportFragmentManager + val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction() + val nameFragment = AdvancedSettingsFragment() + fragmentTransaction.add(R.id.mainActivityLayout, nameFragment) + fragmentTransaction.addToBackStack(null) + fragmentTransaction.commit() + return true + } else -> super.onOptionsItemSelected(item) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt new file mode 100644 index 00000000..6a296ad9 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt @@ -0,0 +1,82 @@ +package com.geeksville.mesh.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import com.geeksville.android.Logging +import com.geeksville.android.hideKeyboard +import com.geeksville.mesh.R +import com.geeksville.mesh.databinding.AdvancedSettingsBinding +import com.geeksville.mesh.model.ChannelOption +import com.geeksville.mesh.model.UIViewModel +import com.geeksville.mesh.service.MeshService +import com.google.android.material.snackbar.Snackbar + +class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging { + private val MAX_INT_DEVICE = 0xFFFFFFFF + private var _binding: AdvancedSettingsBinding? = null + + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! + + private val model: UIViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = AdvancedSettingsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + model.radioConfig.observe(viewLifecycleOwner, { _ -> + binding.positionBroadcastPeriodEditText.setText(model.positionBroadcastSecs.toString()) + binding.lsSleepEditText.setText(model.lsSleepSecs.toString()) + }) + + model.isConnected.observe(viewLifecycleOwner, Observer { connectionState -> + val connected = connectionState == MeshService.ConnectionState.CONNECTED + binding.positionBroadcastPeriodView.isEnabled = connected + binding.lsSleepView.isEnabled = connected + }) + + binding.positionBroadcastPeriodEditText.on(EditorInfo.IME_ACTION_DONE) { + val textEdit = binding.positionBroadcastPeriodEditText + val n = textEdit.text.toString().toIntOrNull() + val minBroadcastPeriodSecs = + ChannelOption.fromConfig(model.radioConfig.value?.channelSettings?.modemConfig)?.minBroadcastPeriodSecs + ?: 9000 + info("edit broadcast $n min $minBroadcastPeriodSecs") + if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) { + model.positionBroadcastSecs = n + } else { + // restore the value in the edit field + textEdit.setText(model.positionBroadcastSecs.toString()) + val errorText = when { + (n == null || n < 0 || n >= MAX_INT_DEVICE) -> "Bad value: ${textEdit.text.toString()}" + else -> getString(R.string.broadcast_period_too_small).format(minBroadcastPeriodSecs) + } + Snackbar.make(requireView(), errorText, Snackbar.LENGTH_LONG).show() + } + requireActivity().hideKeyboard() + } + + binding.lsSleepEditText.on(EditorInfo.IME_ACTION_DONE) { + val str = binding.lsSleepEditText.text.toString() + val n = str.toIntOrNull() + if (n != null && n < MAX_INT_DEVICE && n >= 0) { + model.lsSleepSecs = n + } else { + Snackbar.make(requireView(), "Bad value: $str", Snackbar.LENGTH_LONG).show() + } + requireActivity().hideKeyboard() + } + } +} \ 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 fe0ad40f..9c511b8b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -463,7 +463,6 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { @SuppressLint("NewApi") class SettingsFragment : ScreenFragment("Settings"), Logging { - private val MAX_INT_DEVICE = 0xFFFFFFFF private var _binding: SettingsFragmentBinding? = null // This property is only valid between onCreateView and onDestroyView. @@ -575,28 +574,18 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { /// Setup the ui widgets unrelated to BLE scanning private fun initCommonUI() { - // We want to leave these visible in the IDE, but make sure they default to not visible until we have valid data - binding.positionBroadcastPeriodView.visibility = View.GONE - binding.lsSleepView.visibility = View.GONE model.ownerName.observe(viewLifecycleOwner, { name -> binding.usernameEditText.setText(name) }) - model.radioConfig.observe(viewLifecycleOwner, { _ -> - binding.positionBroadcastPeriodEditText.setText(model.positionBroadcastSecs.toString()) - binding.lsSleepEditText.setText(model.lsSleepSecs.toString()) - }) + // Only let user edit their name or set software update while connected to a radio model.isConnected.observe(viewLifecycleOwner, Observer { connectionState -> val connected = connectionState == MeshService.ConnectionState.CONNECTED binding.usernameView.isEnabled = connected - // Don't even show advanced fields until after we have a connection - binding.positionBroadcastPeriodView.visibility = if (connected) View.VISIBLE else View.GONE - binding.lsSleepView.visibility = if (connected) View.VISIBLE else View.GONE - if (connectionState == MeshService.ConnectionState.DISCONNECTED) model.ownerName.value = "" @@ -620,36 +609,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { requireActivity().hideKeyboard() } - binding.positionBroadcastPeriodEditText.on(EditorInfo.IME_ACTION_DONE) { - val textEdit = binding.positionBroadcastPeriodEditText - val n = textEdit.text.toString().toIntOrNull() - val minBroadcastPeriodSecs = - ChannelOption.fromConfig(model.radioConfig.value?.channelSettings?.modemConfig)?.minBroadcastPeriodSecs - ?: 9000 - info("edit broadcast $n min $minBroadcastPeriodSecs") - if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) { - model.positionBroadcastSecs = n - } else { - // restore the value in the edit field - textEdit.setText(model.positionBroadcastSecs.toString()) - val errorText = if (n == null || n < 0 || n >= MAX_INT_DEVICE) "Bad value: ${textEdit.text.toString()}" else - getString(R.string.broadcast_period_too_small).format(minBroadcastPeriodSecs) - Snackbar.make(requireView(), errorText, Snackbar.LENGTH_LONG).show() - } - - requireActivity().hideKeyboard() - } - - binding.lsSleepEditText.on(EditorInfo.IME_ACTION_DONE) { - val str = binding.lsSleepEditText.text.toString() - val n = str.toIntOrNull() - if (n != null && n < MAX_INT_DEVICE && n >= 0) { - model.lsSleepSecs = n - } else { - binding.scanStatusText.text = "Bad value: $str" - } - requireActivity().hideKeyboard() - } val app = (requireContext().applicationContext as GeeksvilleApplication) diff --git a/app/src/main/res/layout/advanced_settings.xml b/app/src/main/res/layout/advanced_settings.xml new file mode 100644 index 00000000..755b3e58 --- /dev/null +++ b/app/src/main/res/layout/advanced_settings.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 45a7b535..c207a44a 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -152,48 +152,6 @@ app:layout_constraintTop_toBottomOf="@+id/updateFirmwareButton" /> - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index a68555fa..fa17012a 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -16,6 +16,10 @@ android:checkable="true" android:checked="false" android:title="Protocol stress test" /> + Date: Sat, 13 Feb 2021 22:02:24 -0800 Subject: [PATCH 4/4] Formatting --- .../main/java/com/geeksville/mesh/MainActivity.kt | 1 - .../com/geeksville/mesh/model/ChannelOption.kt | 1 + .../mesh/ui/AdvancedSettingsFragment.kt | 15 ++++++++------- .../com/geeksville/mesh/ui/SettingsFragment.kt | 2 -- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index c960a6a9..8fec4932 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -52,7 +52,6 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.tasks.Task import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayoutMediator import com.google.protobuf.InvalidProtocolBufferException import com.vorlonsoft.android.rate.AppRate diff --git a/app/src/main/java/com/geeksville/mesh/model/ChannelOption.kt b/app/src/main/java/com/geeksville/mesh/model/ChannelOption.kt index 0f0e929e..827624cd 100644 --- a/app/src/main/java/com/geeksville/mesh/model/ChannelOption.kt +++ b/app/src/main/java/com/geeksville/mesh/model/ChannelOption.kt @@ -17,5 +17,6 @@ enum class ChannelOption(val modemConfig: MeshProtos.ChannelSettings.ModemConfig } return null } + val defaultMinBroadcastPeriod = VERY_LONG.minBroadcastPeriodSecs } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt index 6a296ad9..4c970bf9 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt @@ -20,7 +20,6 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging { private val MAX_INT_DEVICE = 0xFFFFFFFF private var _binding: AdvancedSettingsBinding? = null - // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! private val model: UIViewModel by activityViewModels() @@ -52,17 +51,19 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging { val n = textEdit.text.toString().toIntOrNull() val minBroadcastPeriodSecs = ChannelOption.fromConfig(model.radioConfig.value?.channelSettings?.modemConfig)?.minBroadcastPeriodSecs - ?: 9000 - info("edit broadcast $n min $minBroadcastPeriodSecs") + ?: ChannelOption.defaultMinBroadcastPeriod + if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) { model.positionBroadcastSecs = n } else { // restore the value in the edit field textEdit.setText(model.positionBroadcastSecs.toString()) - val errorText = when { - (n == null || n < 0 || n >= MAX_INT_DEVICE) -> "Bad value: ${textEdit.text.toString()}" - else -> getString(R.string.broadcast_period_too_small).format(minBroadcastPeriodSecs) - } + val errorText = + if (n == null || n < 0 || n >= MAX_INT_DEVICE) + "Bad value: ${textEdit.text.toString()}" + else + getString(R.string.broadcast_period_too_small).format(minBroadcastPeriodSecs) + Snackbar.make(requireView(), errorText, Snackbar.LENGTH_LONG).show() } requireActivity().hideKeyboard() 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 9c511b8b..73fad2ec 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -34,7 +34,6 @@ import com.geeksville.mesh.R import com.geeksville.mesh.android.bluetoothManager import com.geeksville.mesh.android.usbManager import com.geeksville.mesh.databinding.SettingsFragmentBinding -import com.geeksville.mesh.model.ChannelOption import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.BluetoothInterface import com.geeksville.mesh.service.MeshService @@ -49,7 +48,6 @@ import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationSettingsRequest import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.snackbar.Snackbar import com.hoho.android.usbserial.driver.UsbSerialDriver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers