diff --git a/app/build.gradle b/app/build.gradle index 61cc9403a..695e2ad7d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 20143 // format is Mmmss (where M is 1+the numeric major number - versionName "1.1.43" + versionCode 20144 // format is Mmmss (where M is 1+the numeric major number + versionName "1.1.44" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // per https://developer.android.com/studio/write/vector-asset-studio @@ -121,7 +121,7 @@ dependencies { implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'com.google.android.material:material:1.2.1' + implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' diff --git a/app/src/main/java/com/geeksville/mesh/DataPacket.kt b/app/src/main/java/com/geeksville/mesh/DataPacket.kt index 1b23b7d4c..bbb13f1b2 100644 --- a/app/src/main/java/com/geeksville/mesh/DataPacket.kt +++ b/app/src/main/java/com/geeksville/mesh/DataPacket.kt @@ -126,6 +126,11 @@ data class DataPacket( /** The Node ID for the local node - used for from when sender doesn't know our local node ID */ const val ID_LOCAL = "^local" + /// special broadcast address + const val NODENUM_BROADCAST = (0xffffffff).toInt() + + fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n) + override fun createFromParcel(parcel: Parcel): DataPacket { return DataPacket(parcel) } 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 d72920ed1..45b311f8a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -49,9 +49,6 @@ class MeshService : Service(), Logging { companion object : Logging { - /// special broadcast address - const val NODENUM_BROADCAST = (0xffffffff).toInt() - /// Intents broadcast by MeshService @Deprecated(message = "Does not filter by port number. For legacy reasons only broadcast for UNKNOWN_APP, switch to ACTION_RECEIVED") @@ -71,11 +68,14 @@ class MeshService : Service(), Logging { const val ACTION_MESH_CONNECTED = "$prefix.MESH_CONNECTED" const val ACTION_MESSAGE_STATUS = "$prefix.MESSAGE_STATUS" - class IdNotFoundException(id: String) : Exception("ID not found $id") - class NodeNumNotFoundException(id: Int) : Exception("NodeNum not found $id") + open class NodeNotFoundException(reason: String) : Exception(reason) + class InvalidNodeIdException() : NodeNotFoundException("Invalid NodeId") + class NodeNumNotFoundException(id: Int) : NodeNotFoundException("NodeNum not found $id") + class IdNotFoundException(id: String) : NodeNotFoundException("ID not found $id") /** We treat software update as similar to loss of comms to the regular bluetooth service (so things like sendPosition for background GPS ignores the problem */ - class IsUpdatingException() : RadioNotConnectedException("Operation prohibited during firmware update") + class IsUpdatingException() : + RadioNotConnectedException("Operation prohibited during firmware update") /** * Talk to our running service and try to set a new device address. And then immediately @@ -126,7 +126,7 @@ class MeshService : Service(), Logging { getNodeNum = { myNodeNum } ) - private fun getSenderName(packet : DataPacket?): String { + private fun getSenderName(packet: DataPacket?): String { val name = nodeDBbyID[packet?.from]?.user?.longName return name ?: "Unknown username" } @@ -238,7 +238,7 @@ class MeshService : Service(), Logging { private fun sendToRadio(p: ToRadio.Builder) { val b = p.build().toByteArray() - if(SoftwareUpdateService.isUpdating) + if (SoftwareUpdateService.isUpdating) throw IsUpdatingException() connectedRadio.sendToRadio(b) @@ -255,7 +255,8 @@ class MeshService : Service(), Logging { private fun updateMessageNotification(message: DataPacket) = serviceNotifications.updateMessageNotification( - getSenderName(message), message.bytes!!.toString(utf8)) + getSenderName(message), message.bytes!!.toString(utf8) + ) /** * tell android not to kill us @@ -269,7 +270,8 @@ class MeshService : Service(), Logging { // We always start foreground because that's how our service is always started (if we didn't then android would kill us) // but if we don't really need foreground we immediately stop it. val notification = serviceNotifications.createServiceStateNotification( - notificationSummary) + notificationSummary + ) startForeground(serviceNotifications.notifyId, notification) if (!wantForeground) { @@ -441,20 +443,37 @@ class MeshService : Service(), Logging { n ) - /// Map a nodenum to the nodeid string, or return null if not present or no id found - private fun toNodeID(n: Int) = - if (n == NODENUM_BROADCAST) DataPacket.ID_BROADCAST else nodeDBbyNodeNum[n]?.user?.id + /** Map a nodenum to the nodeid string, or return null if not present + If we have a NodeInfo for this ID we prefer to return the string ID inside the user record. + but some nodes might not have a user record at all (because not yet received), in that case, we return + a hex version of the ID just based on the number */ + private fun toNodeID(n: Int): String? = + if (n == DataPacket.NODENUM_BROADCAST) + DataPacket.ID_BROADCAST + else + nodeDBbyNodeNum[n]?.user?.id ?: DataPacket.nodeNumToDefaultId(n) /// given a nodenum, return a db entry - creating if necessary private fun getOrCreateNodeInfo(n: Int) = nodeDBbyNodeNum.getOrPut(n) { -> NodeInfo(n) } + private val hexIdRegex = """\!([0-9A-Fa-f]+)""".toRegex() + /// Map a userid to a node/ node num, or throw an exception if not found - private fun toNodeInfo(id: String) = - nodeDBbyID[id] - ?: throw IdNotFoundException( - id - ) + /// We prefer to find nodes based on their assigned IDs, but if no ID has been assigned to a node, we can also find it based on node number + private fun toNodeInfo(id: String): NodeInfo { + // If this is a valid hexaddr will be !null + val hexStr = hexIdRegex.matchEntire(id)?.groups?.get(1)?.value + + return nodeDBbyID[id] ?: when { + id == DataPacket.ID_LOCAL -> toNodeInfo(myNodeNum) + hexStr != null -> { + val n = hexStr.toLong(16).toInt() + nodeDBbyNodeNum[n] ?: throw IdNotFoundException(id) + } + else -> throw InvalidNodeIdException() + } + } private val numNodes get() = nodeDBbyNodeNum.size @@ -463,12 +482,11 @@ class MeshService : Service(), Logging { */ private val numOnlineNodes get() = nodeDBbyNodeNum.values.count { it.isOnline } - private fun toNodeNum(id: String) = - when (id) { - DataPacket.ID_BROADCAST -> NODENUM_BROADCAST - DataPacket.ID_LOCAL -> myNodeNum - else -> toNodeInfo(id).num - } + private fun toNodeNum(id: String): Int = when (id) { + DataPacket.ID_BROADCAST -> DataPacket.NODENUM_BROADCAST + DataPacket.ID_LOCAL -> myNodeNum + else -> toNodeInfo(id).num + } /// A helper function that makes it easy to update node info objects private fun updateNodeInfo(nodeNum: Int, updatefn: (NodeInfo) -> Unit) { @@ -502,7 +520,7 @@ class MeshService : Service(), Logging { from = myNodeNum // We might need to change broadcast addresses to work with old device loads - to = if (useShortAddresses && idNum == NODENUM_BROADCAST) 255 else idNum + to = if (useShortAddresses && idNum == DataPacket.NODENUM_BROADCAST) 255 else idNum } /** @@ -588,7 +606,7 @@ class MeshService : Service(), Logging { private fun rememberDataPacket(dataPacket: DataPacket) { // Now that we use data packets for more things, we need to be choosier about what we keep. Since (currently - in the future less so) // we only care about old text messages, we just store those... - if(dataPacket.dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) { + if (dataPacket.dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) { // discard old messages if needed then add the new one while (recentDataPackets.size > 50) recentDataPackets.removeAt(0) @@ -617,7 +635,11 @@ class MeshService : Service(), Logging { // Handle position updates from the device if (data.portnumValue == Portnums.PortNum.POSITION_APP_VALUE) { val rxTime = if (packet.rxTime != 0) packet.rxTime else currentSecond() - handleReceivedPosition(packet.from, MeshProtos.Position.parseFrom(data.payload), rxTime) + handleReceivedPosition( + packet.from, + MeshProtos.Position.parseFrom(data.payload), + rxTime + ) } else debug("Ignoring packet sent from our node, portnum=${data.portnumValue} ${bytes.size} bytes") } else { @@ -911,7 +933,11 @@ class MeshService : Service(), Logging { // Do our startup init try { connectTimeMsec = System.currentTimeMillis() - SoftwareUpdateService.sendProgress(this, ProgressNotStarted, true) // Kinda crufty way of reiniting software update + SoftwareUpdateService.sendProgress( + this, + ProgressNotStarted, + true + ) // Kinda crufty way of reiniting software update startConfig() } catch (ex: InvalidProtocolBufferException) { @@ -995,7 +1021,7 @@ class MeshService : Service(), Logging { val proto = MeshProtos.FromRadio.parseFrom(bytes) // info("Received from radio service: ${proto.toOneLineString()}") - when (proto.variantCase.number) { + when (proto.payloadVariantCase.number) { MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket( proto.packet ) @@ -1144,7 +1170,6 @@ class MeshService : Service(), Logging { } - /** * If we are updating nodes we might need to use old (fixed by firmware build) * region info to populate our new universal ROMs. @@ -1252,7 +1277,7 @@ class MeshService : Service(), Logging { lat: Double, lon: Double, alt: Int, - destNum: Int = NODENUM_BROADCAST, + destNum: Int = DataPacket.NODENUM_BROADCAST, wantResponse: Boolean = false ) { debug("Sending our position to=$destNum lat=$lat, lon=$lon, alt=$alt") @@ -1269,7 +1294,8 @@ class MeshService : Service(), Logging { val packet = newMeshPacketTo(destNum) packet.decoded = MeshProtos.SubPacket.newBuilder().also { - val isNewPositionAPI = deviceVersion >= DeviceVersion("1.20.0") // We changed position APIs with this version + val isNewPositionAPI = + deviceVersion >= DeviceVersion("1.20.0") // We changed position APIs with this version if (isNewPositionAPI) { // Use the new position as data format it.data = makeData(Portnums.PortNum.POSITION_APP_VALUE, position.toByteString()) @@ -1291,13 +1317,12 @@ class MeshService : Service(), Logging { lat: Double, lon: Double, alt: Int, - destNum: Int = NODENUM_BROADCAST, + destNum: Int = DataPacket.NODENUM_BROADCAST, wantResponse: Boolean = false ) = serviceScope.handledLaunch { try { sendPosition(lat, lon, alt, destNum, wantResponse) - } - catch(ex: RadioNotConnectedException) { + } catch (ex: RadioNotConnectedException) { warn("Ignoring disconnected radio during gps location update") } } @@ -1371,13 +1396,10 @@ class MeshService : Service(), Logging { if (currentPacketId == 0L) { logAssert(it.packetIdBits == 8 || it.packetIdBits == 32) // Only values I'm expecting (though we don't require this) - val devicePacketId = if (it.currentPacketId == 0L) { - // Old devices don't send their current packet ID, in that case just pick something random and it will probably be fine ;-) - val random = Random(System.currentTimeMillis()) - random.nextLong().absoluteValue - } else - it.currentPacketId - + // We now always pick a random initial packet id (odds of collision with the device is insanely low with 32 bit ids) + val random = Random(System.currentTimeMillis()) + val devicePacketId = random.nextLong().absoluteValue + // Not inited - pick a number on the opposite side of what the device is using currentPacketId = devicePacketId + numPacketIds / 2 } else { diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceLocationCallback.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceLocationCallback.kt index 2549bc652..4733557e0 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceLocationCallback.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceLocationCallback.kt @@ -2,6 +2,7 @@ package com.geeksville.mesh.service import android.location.Location import android.os.RemoteException +import com.geeksville.mesh.DataPacket import com.google.android.gms.location.LocationCallback import com.google.android.gms.location.LocationResult @@ -37,7 +38,7 @@ class MeshServiceLocationCallback( MeshService.info("got phone location") if (location.isAccurateForMesh) { // if within 200 meters, or accuracy is unknown val shouldSend = isAllowedToSend() - val destinationNumber = if (shouldSend) MeshService.NODENUM_BROADCAST else getNodeNum() + val destinationNumber = if (shouldSend) DataPacket.NODENUM_BROADCAST else getNodeNum() sendPosition(location, destinationNumber, wantResponse = shouldSend) } else { MeshService.warn("accuracy ${location.accuracy} is too poor to use") diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt index c7b315535..5e02067a4 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt @@ -152,7 +152,7 @@ class MeshServiceNotifications( fun createServiceStateNotification(summaryString: String): Notification { val builder = commonBuilder(channelId) with(builder) { - setPriority(NotificationCompat.PRIORITY_MIN) + priority = NotificationCompat.PRIORITY_MIN setCategory(Notification.CATEGORY_SERVICE) setOngoing(true) setContentTitle(summaryString) // leave this off for now so our notification looks smaller @@ -163,10 +163,11 @@ class MeshServiceNotifications( fun createMessageNotifcation(name: String, message: String): Notification { val builder = commonBuilder(messageChannelId) with(builder) { - setPriority(NotificationCompat.PRIORITY_DEFAULT) + priority = NotificationCompat.PRIORITY_DEFAULT setCategory(Notification.CATEGORY_MESSAGE) setAutoCancel(true) setContentTitle(name) + setContentText(message) setStyle( NotificationCompat.BigTextStyle() .bigText(message), diff --git a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt index ade70bf6c..5eaf012c6 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MockInterface.kt @@ -1,10 +1,7 @@ package com.geeksville.mesh.service import com.geeksville.android.Logging -import com.geeksville.mesh.MeshProtos -import com.geeksville.mesh.Portnums -import com.geeksville.mesh.Position -import com.geeksville.mesh.R +import com.geeksville.mesh.* import com.geeksville.mesh.model.getInitials import com.google.protobuf.ByteString import okhttp3.internal.toHexString @@ -89,7 +86,7 @@ class MockInterface(private val service: RadioInterfaceService) : Logging, IRadi nodeInfo = MeshProtos.NodeInfo.newBuilder().apply { num = numIn user = MeshProtos.User.newBuilder().apply { - id = "!0x" + num.toHexString() + id = DataPacket.nodeNumToDefaultId(numIn) longName = "Sim " + num.toHexString() shortName = getInitials(longName) }.build() 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 5572d3cb5..486acbfc1 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -134,6 +134,12 @@ class MapFragment : ScreenFragment("Map"), Logging { var mapView: MapView? = null + /** + * Mapbox native code can crash painfully if you ever call a mapbox view function while the view is not actively being show + */ + private val isViewVisible: Boolean + get() = !(mapView?.isDestroyed ?: true) + override fun onViewCreated(viewIn: View, savedInstanceState: Bundle?) { super.onViewCreated(viewIn, savedInstanceState) @@ -169,7 +175,8 @@ class MapFragment : ScreenFragment("Map"), Logging { // Any times nodes change update our map model.nodeDB.nodes.observe(viewLifecycleOwner, Observer { nodes -> - onNodesChanged(map, nodes.values) + if(isViewVisible) + onNodesChanged(map, nodes.values) }) zoomToNodes(map) } diff --git a/app/src/main/proto b/app/src/main/proto index 106f4bfde..8492e4030 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit 106f4bfdebe277ab0b86d2b8c950ab78a35b0654 +Subproject commit 8492e4030ad928ee5fc97f8ead95325dba7f9492 diff --git a/geeksville-androidlib b/geeksville-androidlib index e398a0759..ee6611838 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit e398a075921fbe051a22463c3052abdd802c3535 +Subproject commit ee6611838637d096b39e11365930eb90a5f0fd2e