diff --git a/res/values/strings.xml b/res/values/strings.xml index be09ad2..3c887fe 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -240,6 +240,7 @@ UDP (send only) Bluetooth SPP +Bluetooth Low Energy TCP/IP USB Serial @@ -360,7 +361,7 @@ Connected: %s Messaging Preferences -Set Messaging Retry & Ack Options +Advanced Messaging Options Message Retries Number of messages to retry Message retry limit @@ -369,13 +370,19 @@ Retry interval start rate Rate doubles each retry -Ack Dupe Timeout +Ack Dupe Timeout or Disable Ack dupe timeout (0 = ack disabled) -Dupe timeout +Dupe acks allowed after timeout Ack Dupe Timeout -Enable +Sends dupe acks after the timeout period +Duplicate Messages +Allows dupe messages after timeout period + +Duplicate messages allowed after timeout +Dupe message timeout (0 = disabled) +Dupe Message Timeout APRS digi path diff --git a/res/xml/messaging.xml b/res/xml/messaging.xml index 942ebda..bdaf756 100644 --- a/res/xml/messaging.xml +++ b/res/xml/messaging.xml @@ -34,5 +34,20 @@ android:summary="@string/p_ackdupe_interval_summary" android:dialogTitle="@string/p_ackdupe_interval_entry" /> + + + + diff --git a/src/MessagingPrefs.scala b/src/MessagingPrefs.scala index 05ede96..ef6f09f 100644 --- a/src/MessagingPrefs.scala +++ b/src/MessagingPrefs.scala @@ -30,7 +30,7 @@ class MessagingPrefs extends PreferenceActivity with OnSharedPreferenceChangeLis // Called when a shared preference is changed override def onSharedPreferenceChanged(sp: SharedPreferences, key: String): Unit = { key match { - case "p.messaging" | "p.retry" | "p.ackdupetoggle" | "p.ackdupe" => + case "p.messaging" | "p.retry" | "p.ackdupetoggle" | "p.ackdupe" | "p.msgdupetoggle" | "p.msgdupetime" => setPreferenceScreen(null) // Clear the current preference screen loadXml() // Reload the preferences to reflect any changes case _ => // Ignore other keys diff --git a/src/PrefsWrapper.scala b/src/PrefsWrapper.scala index 60ce981..ef59f45 100644 --- a/src/PrefsWrapper.scala +++ b/src/PrefsWrapper.scala @@ -21,6 +21,9 @@ class PrefsWrapper(val context : Context) { def isAckDupeEnabled(): Boolean = { prefs.getBoolean("p.ackdupetoggle", false) } + def isMsgDupeEnabled(): Boolean = { + prefs.getBoolean("p.msgdupetoggle", false) + } def isMetric(): Boolean = { prefs.getString("p.units", "1") == "1" // "1" for metric, "2" for imperial } diff --git a/src/StorageDatabase.scala b/src/StorageDatabase.scala index dc9600b..101a05e 100644 --- a/src/StorageDatabase.scala +++ b/src/StorageDatabase.scala @@ -7,6 +7,7 @@ import _root_.android.database.sqlite.SQLiteDatabase import _root_.android.database.Cursor import _root_.android.util.Log import _root_.android.widget.FilterQueryProvider +import _root_.android.preference.PreferenceManager import _root_.net.ab0oo.aprs.parser._ @@ -183,6 +184,8 @@ class StorageDatabase(context : Context) extends null, StorageDatabase.DB_VERSION) { import StorageDatabase._ + lazy val prefs = new PrefsWrapper(context) + override def onCreate(db: SQLiteDatabase) { Log.d(TAG, "onCreate(): creating new database " + DB_NAME); db.execSQL(Post.TABLE_CREATE); @@ -259,21 +262,47 @@ class StorageDatabase(context : Context) extends getWritableDatabase().replaceOrThrow(TABLE, CALL, cv) } - def isMessageDuplicate(call : String, msgid : String, text : String) : Boolean = { - val c = getReadableDatabase().query(Message.TABLE, Message.COLUMNS, - "type = 1 AND call = ? AND msgid = ? AND text = ?", - Array(call, msgid, text), - null, null, - null, null) - val result = (c.getCount() > 0) - c.close() - result + def isMessageDuplicate(call: String, msgid: String, text: String, currentTs: Long): Boolean = { + // Prepare the base query conditions + val selection = "type = 1 AND call = ? AND msgid = ? AND text = ?" + val selectionArgs = Array(call, msgid, text) + var query: String = selection + var args: Array[String] = selectionArgs + + // If message duplication is enabled, add the time threshold condition + if (prefs.isMsgDupeEnabled) { + val msgDupetime = prefs.getStringInt("p.msgdupetime", 30) + val timeThreshold = currentTs - (msgDupetime * 1000) + // If msgDupetime is 0, return false immediately (no duplication check) + if (msgDupetime == 0) { + return false + } + query += " AND ts >= ?" + args = args :+ timeThreshold.toString + } + + // Perform the database query with the constructed query and args + val cursor = getReadableDatabase().query( + Message.TABLE, + Message.COLUMNS, + query, + args, + null, null, null, null + ) + + // Check if there are any results (cursor.getCount() could be replaced with cursor.moveToFirst()) + val result = cursor.moveToFirst() // Checks if at least one record is found + cursor.close() + + result } - // add an incoming message, returns false if duplicate - def addMessage(ts : Long, srccall : String, msg : MessagePacket) : Boolean = { + + // Add an incoming message, returns false if duplicate + def addMessage(ts: Long, srccall: String, msg: MessagePacket): Boolean = { import Message._ - if (isMessageDuplicate(srccall, msg.getMessageNumber(), msg.getMessageBody())) { + // Check if the message is a duplicate considering timestamp + if (isMessageDuplicate(srccall, msg.getMessageNumber(), msg.getMessageBody(), ts)) { Log.i(TAG, "received duplicate message from %s: %s".format(srccall, msg)) return false }