diff --git a/app/src/main/java/com/geeksville/mesh/DataPacket.kt b/app/src/main/java/com/geeksville/mesh/DataPacket.kt new file mode 100644 index 000000000..5996309c6 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/DataPacket.kt @@ -0,0 +1,57 @@ +package com.geeksville.mesh + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable + +@Parcelize +enum class MessageStatus : Parcelable { + UNKNOWN, // Not set for this message + RECEIVED, // Came in from the mesh + QUEUED, // Waiting to send to the mesh as soon as we connect to the device + ENROUTE, // Delivered to the radio, but no ACK or NAK received + DELIVERED // We received an ack +} + +/** + * A parcelable version of the protobuf MeshPacket + Data subpacket. + */ +@Serializable +@Parcelize +data class DataPacket( + val from: String, // a nodeID string + val to: String, // a nodeID string + val rxTime: Long, // msecs since 1970 + val id: Int, + val dataType: Int, + val bytes: ByteArray, + val status: MessageStatus = MessageStatus.UNKNOWN +) : Parcelable { + + // Autogenerated comparision, because we have a byte array + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DataPacket + + if (from != other.from) return false + if (to != other.to) return false + if (rxTime != other.rxTime) return false + if (id != other.id) return false + if (dataType != other.dataType) return false + if (!bytes.contentEquals(other.bytes)) return false + + return true + } + + override fun hashCode(): Int { + var result = from.hashCode() + result = 31 * result + to.hashCode() + result = 31 * result + rxTime.hashCode() + result = 31 * result + id + result = 31 * result + dataType + result = 31 * result + bytes.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index d67e9f048..0a3ce55d5 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -3,73 +3,22 @@ package com.geeksville.mesh import android.os.Parcelable import com.geeksville.mesh.ui.bearing import com.geeksville.mesh.ui.latLongToMeter +import com.geeksville.util.anonymize import kotlinx.android.parcel.Parcelize import kotlinx.serialization.Serializable -/** - * When printing strings to logs sometimes we want to print useful debugging information about users - * or positions. But we don't want to leak things like usernames or locations. So this function - * if given a string, will return a string which is a maximum of three characters long, taken from the tail - * of the string. Which should effectively hide real usernames and locations, - * but still let us see if values were zero, empty or different. - */ -val Any?.anonymized: String - get() = if (this != null) ("..." + this.toString().takeLast(3)) else "null" - // // model objects that directly map to the corresponding protobufs // -/** - * A parcelable version of the protobuf MeshPacket + Data subpacket. - */ -@Serializable -@Parcelize -data class DataPacket( - val from: String, // a nodeID string - val to: String, // a nodeID string - val rxTime: Long, // msecs since 1970 - val id: Int, - val dataType: Int, - val bytes: ByteArray -) : Parcelable { - - // Autogenerated comparision, because we have a byte array - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as DataPacket - - if (from != other.from) return false - if (to != other.to) return false - if (rxTime != other.rxTime) return false - if (id != other.id) return false - if (dataType != other.dataType) return false - if (!bytes.contentEquals(other.bytes)) return false - - return true - } - - override fun hashCode(): Int { - var result = from.hashCode() - result = 31 * result + to.hashCode() - result = 31 * result + rxTime.hashCode() - result = 31 * result + id - result = 31 * result + dataType - result = 31 * result + bytes.contentHashCode() - return result - } -} - @Serializable @Parcelize data class MeshUser(val id: String, val longName: String, val shortName: String) : Parcelable { override fun toString(): String { - return "MeshUser(id=${id.anonymized}, longName=${longName.anonymized}, shortName=${shortName.anonymized})" + return "MeshUser(id=${id.anonymize}, longName=${longName.anonymize}, shortName=${shortName.anonymize})" } } @@ -106,7 +55,7 @@ data class Position( fun bearing(o: Position) = bearing(latitude, longitude, o.latitude, o.longitude) override fun toString(): String { - return "Position(lat=${latitude.anonymized}, lon=${longitude.anonymized}, alt=${altitude.anonymized}, time=${time})" + return "Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=${time})" } } 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 610a9e57c..65a75d113 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1256,7 +1256,7 @@ class MeshService : Service(), Logging { * Set our owner with either the new or old API */ fun setOwner(myId: String?, longName: String, shortName: String) { - debug("SetOwner $myId : ${longName.anonymized} : $shortName") + debug("SetOwner $myId : ${longName.anonymize} : $shortName") val user = MeshProtos.User.newBuilder().also { if (myId != null) // Only set the id if it was provided diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index 33662ebdc..e42d1c292 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -19,7 +19,7 @@ import com.geeksville.android.Logging import com.geeksville.concurrent.DeferredExecution import com.geeksville.concurrent.handledLaunch import com.geeksville.mesh.IRadioInterfaceService -import com.geeksville.mesh.anonymized +import com.geeksville.util.anonymize import com.geeksville.util.exceptionReporter import com.geeksville.util.ignoreException import com.geeksville.util.toRemoteExceptions @@ -191,7 +191,7 @@ class RadioInterfaceService : Service(), Logging { val address = getPrefs(context).getString(DEVADDR_KEY, null) if (address != null && !allPaired.contains(address)) { - warn("Ignoring stale bond to ${address.anonymized}") + warn("Ignoring stale bond to ${address.anonymize}") null } else address @@ -560,7 +560,7 @@ class RadioInterfaceService : Service(), Logging { // device is off/not connected) val device = getBluetoothAdapter(this)?.getRemoteDevice(address) if (device != null) { - info("Creating radio interface service. device=${address.anonymized}") + info("Creating radio interface service. device=${address.anonymize}") // Note this constructor also does no comm val s = SafeBluetooth(this, device) diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index 20ffff62c..ed41db64e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -6,6 +6,8 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.widget.EditText +import android.widget.ImageView +import android.widget.TextView import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager @@ -15,6 +17,7 @@ import com.geeksville.mesh.R import com.geeksville.mesh.model.TextMessage import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.MeshService +import com.google.android.material.chip.Chip import kotlinx.android.synthetic.main.adapter_message_layout.view.* import kotlinx.android.synthetic.main.messages_fragment.* import java.text.SimpleDateFormat @@ -38,9 +41,10 @@ class MessagesFragment : ScreenFragment("Messages"), Logging { // Provide a direct reference to each of the views within a data item // Used to cache the views within the item layout for fast access class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val username = itemView.username - val messageText = itemView.messageText - val messageTime = itemView.messageTime + val username: Chip = itemView.username + val messageText: TextView = itemView.messageText + val messageTime: TextView = itemView.messageTime + val messageStatusIcon: ImageView = itemView.messageStatusIcon } private val messagesAdapter = object : RecyclerView.Adapter() { 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 51d29997b..2f4d97615 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -28,10 +28,10 @@ import com.geeksville.android.hideKeyboard import com.geeksville.concurrent.handledLaunch import com.geeksville.mesh.MainActivity import com.geeksville.mesh.R -import com.geeksville.mesh.anonymized import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.service.RadioInterfaceService +import com.geeksville.util.anonymize import com.geeksville.util.exceptionReporter import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.android.synthetic.main.settings_fragment.* @@ -102,7 +102,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { // val isSelected get() = macAddress == selectedMacAddr override fun toString(): String { - return "BTScanEntry(name=${name.anonymized}, addr=${macAddress.anonymized})" + return "BTScanEntry(name=${name.anonymize}, addr=${macAddress.anonymize})" } } @@ -282,7 +282,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { /// Change to a new macaddr selection, updating GUI and radio fun changeScanSelection(context: MainActivity, newAddr: String) { - info("Changing BT device to ${newAddr.anonymized}") + info("Changing BT device to ${newAddr.anonymize}") selectedMacAddr = newAddr changeDeviceSelection(context, newAddr) } diff --git a/app/src/main/res/drawable/ic_twotone_cloud_24.xml b/app/src/main/res/drawable/ic_twotone_cloud_24.xml new file mode 100644 index 000000000..d6cd3cf01 --- /dev/null +++ b/app/src/main/res/drawable/ic_twotone_cloud_24.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/layout/adapter_message_layout.xml b/app/src/main/res/layout/adapter_message_layout.xml index 25d9ded76..8afd8c6a7 100644 --- a/app/src/main/res/layout/adapter_message_layout.xml +++ b/app/src/main/res/layout/adapter_message_layout.xml @@ -1,11 +1,12 @@ + android:clipToPadding="false" + android:contentDescription="Message delivery status" + android:elevation="2dp"> + app:layout_constraintStart_toStartOf="@+id/username" + app:layout_constraintTop_toBottomOf="@+id/username" /> + + diff --git a/geeksville-androidlib b/geeksville-androidlib index 81fee94de..90cf0a037 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit 81fee94de954fa4d8d942fff8fb929b9ef981a2f +Subproject commit 90cf0a03708d5cf8a564a8044e0ab2ced6ccef52