diff --git a/TODO.md b/TODO.md index 886df1090..c8d9fa6fe 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,6 @@ # Remaining tasks before declaring 1.0 * add a low level settings screen (let user change any of the RadioConfig parameters) -* if user edits the channel, generate a new AES256 key * add play store link with https://developers.google.com/analytics/devguides/collection/android/v4/campaigns#google-play-url-builder and the play icon Things for the betaish period. diff --git a/app/build.gradle b/app/build.gradle index 9602a5d70..d1a6618b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 176 - versionName "0.7.6" + versionCode 10771 // format is Mmmss (where M is 1+the numeric major number + versionName "0.7.71" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/java/com/geeksville/mesh/model/Channel.kt b/app/src/main/java/com/geeksville/mesh/model/Channel.kt index a0323a8c9..00d226d7a 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Channel.kt @@ -14,10 +14,12 @@ data class Channel( val settings: MeshProtos.ChannelSettings = MeshProtos.ChannelSettings.getDefaultInstance() ) { companion object { + // Note: this string _SHOULD NOT BE LOCALIZED_ because it directly hashes to values used on the device for the default channel name. + val defaultChannelName = "Default" + // Placeholder when emulating val emulated = Channel( - // Note: this string _SHOULD NOT BE LOCALIZED_ because it directly hashes to values used on the device for the default channel name. - MeshProtos.ChannelSettings.newBuilder().setName("Default") + MeshProtos.ChannelSettings.newBuilder().setName(defaultChannelName) .setModemConfig(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128).build() ) 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 a044b94c4..a855e167d 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -620,6 +620,7 @@ class MeshService : Service(), Logging { private var radioConfig: MeshProtos.RadioConfig? = null /// True after we've done our initial node db init + @Volatile private var haveNodeDB = false // The database of active nodes, index is the node number @@ -933,6 +934,7 @@ class MeshService : Service(), Logging { // decided to pass through to us (except for broadcast packets) //val toNum = packet.to + debug("Recieved: $packet") val p = packet.decoded // If the rxTime was not set by the device (because device software was old), guess at a time @@ -1154,29 +1156,30 @@ class MeshService : Service(), Logging { } RadioInterfaceService.RECEIVE_FROMRADIO_ACTION -> { - val proto = - MeshProtos.FromRadio.parseFrom( - intent.getByteArrayExtra( - EXTRA_PAYLOAD - )!! - ) - info("Received from radio service: ${proto.toOneLineString()}") - when (proto.variantCase.number) { - MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket( - proto.packet - ) + val bytes = intent.getByteArrayExtra(EXTRA_PAYLOAD)!! + try { + val proto = + MeshProtos.FromRadio.parseFrom(bytes) + info("Received from radio service: ${proto.toOneLineString()}") + when (proto.variantCase.number) { + MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket( + proto.packet + ) - MeshProtos.FromRadio.CONFIG_COMPLETE_ID_FIELD_NUMBER -> handleConfigComplete( - proto.configCompleteId - ) + MeshProtos.FromRadio.CONFIG_COMPLETE_ID_FIELD_NUMBER -> handleConfigComplete( + proto.configCompleteId + ) - MeshProtos.FromRadio.MY_INFO_FIELD_NUMBER -> handleMyInfo(proto.myInfo) + MeshProtos.FromRadio.MY_INFO_FIELD_NUMBER -> handleMyInfo(proto.myInfo) - MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo) + MeshProtos.FromRadio.NODE_INFO_FIELD_NUMBER -> handleNodeInfo(proto.nodeInfo) - MeshProtos.FromRadio.RADIO_FIELD_NUMBER -> handleRadioConfig(proto.radio) + MeshProtos.FromRadio.RADIO_FIELD_NUMBER -> handleRadioConfig(proto.radio) - else -> errormsg("Unexpected FromRadio variant") + else -> errormsg("Unexpected FromRadio variant") + } + } catch (ex: InvalidProtocolBufferException) { + Exceptions.report(ex, "Invalid Protobuf from radio, len=${bytes.size}") } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index b012fb741..b4c3ba701 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -18,12 +18,15 @@ import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.android.hideKeyboard import com.geeksville.mesh.R +import com.geeksville.mesh.model.Channel import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.MeshService import com.geeksville.util.Exceptions import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar +import com.google.protobuf.ByteString import kotlinx.android.synthetic.main.channel_fragment.* +import java.security.SecureRandom // Make an image view dim @@ -142,7 +145,15 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { UIViewModel.getChannel(model.radioConfig.value)?.let { old -> val newSettings = old.settings.toBuilder() newSettings.name = channelNameEdit.text.toString().trim() - // FIXME, regenerate a new preshared key! + + // Generate a new AES256 key (for any channel not named Default) + if (newSettings.name != Channel.defaultChannelName) { + debug("ASSIGNING NEW AES256 KEY") + val random = SecureRandom() + val bytes = ByteArray(32) + random.nextBytes(bytes) + newSettings.psk = ByteString.copyFrom(bytes) + } // Try to change the radio, if it fails, tell the user why and throw away their redits try { @@ -150,7 +161,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { // Since we are writing to radioconfig, that will trigger the rest of the GUI update (QR code etc) } catch (ex: RemoteException) { setGUIfromModel() // Throw away user edits - + // Tell the user to try again Snackbar.make( editableCheckbox, diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index e0fa4dd5c..76b5a31b3 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -6,7 +6,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Observer import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.mesh.NodeInfo @@ -131,7 +130,7 @@ class MapFragment : ScreenFragment("Map"), Logging { } var mapView: MapView? = null - + var mapboxMap: MapboxMap? = null override fun onViewCreated(viewIn: View, savedInstanceState: Bundle?) { super.onViewCreated(viewIn, savedInstanceState) @@ -146,6 +145,7 @@ class MapFragment : ScreenFragment("Map"), Logging { // Each time the pane is shown start fetching new map info (we do this here instead of // onCreate because getMapAsync can die in native code if the view goes away) v.getMapAsync { map -> + mapboxMap = map if (view != null) { // it might have gone away by now // val markerIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_twotone_person_pin_24) @@ -159,11 +159,6 @@ class MapFragment : ScreenFragment("Map"), Logging { style.addLayer(labelLayer) } - model.nodeDB.nodes.observe(viewLifecycleOwner, Observer { nodes -> - if (view != null) - onNodesChanged(map, nodes.values) - }) - //map.uiSettings.isScrollGesturesEnabled = true //map.uiSettings.isZoomGesturesEnabled = true } @@ -190,6 +185,16 @@ class MapFragment : ScreenFragment("Map"), Logging { override fun onResume() { super.onResume() mapView?.onResume() + + // FIXME: for now we just set the node positions when the user pages to the map - because + // otherwise we try to update in the background which breaks mapbox native code (when view is not shown + mapboxMap?.let { map -> + // model.nodeDB.nodes.observe(viewLifecycleOwner, Observer { nodes -> + model.nodeDB.nodes.value?.values?.let { + onNodesChanged(map, it) + } + //}) + } } override fun onDestroy() { 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 c4334e08e..f960190b8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -574,7 +574,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { b.text = device.name b.id = View.generateViewId() b.isEnabled = enabled - b.isChecked = device.address == scanModel.selectedNotNull + b.isChecked = + device.address == scanModel.selectedNotNull && device.bonded // Only show checkbox if device is still paired deviceRadioGroup.addView(b) // Once we have at least one device, don't show the "looking for" animation - it makes uers think