-
Notifications
You must be signed in to change notification settings - Fork 96
/
MessageService.scala
125 lines (109 loc) · 4.53 KB
/
MessageService.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package org.aprsdroid.app
import _root_.android.content.{BroadcastReceiver, ContentValues, Context, Intent}
import _root_.android.util.Log
import _root_.android.os.Handler
import _root_.net.ab0oo.aprs.parser._
class MessageService(s : AprsService) {
val TAG = "APRSdroid.MsgService"
val NUM_OF_RETRIES = 7
val pendingSender = new Runnable() { override def run() { sendPendingMessages() } }
def createMessageNotifier() = new BroadcastReceiver() {
override def onReceive(ctx : Context, i : Intent) {
sendPendingMessages()
}
}
def storeNotifyMessage(ts : Long, srccall : String, msg : MessagePacket) {
val is_new = s.db.addMessage(ts, srccall, msg)
if (is_new)
ServiceNotifier.instance.notifyMessage(s, s.prefs, srccall, msg.getMessageBody())
}
def handleMessage(ts : Long, ap : APRSPacket, msg : MessagePacket) {
val callssid = s.prefs.getCallSsid()
if (ap.getSourceCall().equalsIgnoreCase(callssid)) {
Log.i(TAG, "ignoring own digipeated message")
return
}
if (msg.getTargetCallsign().equalsIgnoreCase(callssid)) {
if (msg.isAck() || msg.isRej()) {
val new_type = if (msg.isAck())
StorageDatabase.Message.TYPE_OUT_ACKED
else
StorageDatabase.Message.TYPE_OUT_REJECTED
s.db.updateMessageAcked(ap.getSourceCall(), msg.getMessageNumber(), new_type)
} else {
storeNotifyMessage(ts, ap.getSourceCall(), msg)
if (msg.getMessageNumber() != "") {
// we need to send an ack
val ack = AprsPacket.formatMessage(callssid, s.appVersion(), ap.getSourceCall(), "ack", msg.getMessageNumber())
val status = s.poster.update(ack)
s.addPost(StorageDatabase.Post.TYPE_POST, status, ack.toString)
}
}
s.sendBroadcast(new Intent(AprsService.MESSAGE).putExtra(AprsService.STATUS, ap.toString))
} else if (msg.getTargetCallsign().split("-")(0).equalsIgnoreCase(
s.prefs.getCallsign()) && !msg.isAck() && !msg.isRej()) {
// incoming message for a different ssid of our callsign
Log.d(TAG, "incoming message for " + msg.getTargetCallsign())
storeNotifyMessage(ts, ap.getSourceCall(), msg)
s.sendBroadcast(new Intent(AprsService.MESSAGE).putExtra(AprsService.STATUS, ap.toString))
}
}
// return 2^n * 30s, at most 32min
def getRetryDelayMS(retrycnt : Int) = 30000 * (1 << math.min(retrycnt - 1, 6))
def scheduleNextSend(delay : Long) {
// add some time to prevent fast looping
Log.d(TAG, "scheduling TX in " + (delay+999)/1000 + "s")
s.handler.postDelayed(pendingSender, (delay+999)/1000*1000)
}
// called when the service is terminated, we have to clean up timers
def stop() {
s.handler.removeCallbacks(pendingSender)
}
def sendPendingMessages() {
import StorageDatabase.Message._
s.handler.removeCallbacks(pendingSender)
// when to schedule next send round
var next_run = Long.MaxValue
val callssid = s.prefs.getCallSsid()
val c = s.db.getPendingMessages(NUM_OF_RETRIES)
//Log.d(TAG, "sendPendingMessages")
c.moveToFirst()
while (!c.isAfterLast()) {
val ts = c.getLong(COLUMN_TS)
val retrycnt = c.getInt(COLUMN_RETRYCNT)
val call = c.getString(COLUMN_CALL)
val msgid = c.getString(COLUMN_MSGID)
val msgtype = c.getInt(COLUMN_TYPE)
val text = c.getString(COLUMN_TEXT)
val t_send = ts + getRetryDelayMS(retrycnt) - System.currentTimeMillis()
Log.d(TAG, "pending message: %d/%d (%ds) ->%s '%s'".format(retrycnt, NUM_OF_RETRIES,
t_send/1000, call, text))
if (retrycnt == NUM_OF_RETRIES && t_send <= 0) {
// this message timed out
s.db.updateMessageType(c.getLong(/* COLUMN_ID */ 0), TYPE_OUT_ABORTED)
s.sendBroadcast(new Intent(AprsService.MESSAGE))
} else if (retrycnt < NUM_OF_RETRIES && t_send <= 0) {
// this message needs to be transmitted
val msg = AprsPacket.formatMessage(callssid, s.appVersion(), call, text, msgid)
val status = s.poster.update(msg)
s.addPost(StorageDatabase.Post.TYPE_POST, status, msg.toString)
val cv = new ContentValues()
cv.put(RETRYCNT, (retrycnt + 1).asInstanceOf[java.lang.Integer])
cv.put(TS, System.currentTimeMillis.asInstanceOf[java.lang.Long])
// XXX: do not ack until acked
s.db.updateMessage(c.getLong(/* COLUMN_ID */ 0), cv)
s.sendBroadcast(new Intent(AprsService.MESSAGE).putExtra(AprsService.STATUS, msg.toString))
// schedule potential re-transmission
next_run = math.min(next_run, getRetryDelayMS(retrycnt + 1))
} else if (retrycnt < NUM_OF_RETRIES) {
// schedule transmission
next_run = math.min(next_run, t_send)
}
c.moveToNext()
}
c.close()
// reschedule transmission
if (next_run != Long.MaxValue)
scheduleNextSend(next_run)
}
}