Skip to content

Commit cba66ad

Browse files
committed
Add retry logic for received messages and sent events
1 parent 11eea8d commit cba66ad

File tree

6 files changed

+204
-41
lines changed

6 files changed

+204
-41
lines changed

android/app/src/main/java/com/httpsms/Constants.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ class Constants {
88
const val KEY_MESSAGE_SIM = "KEY_MESSAGE_SIM"
99
const val KEY_MESSAGE_CONTENT = "KEY_MESSAGE_CONTENT"
1010
const val KEY_MESSAGE_TIMESTAMP = "KEY_MESSAGE_TIMESTAMP"
11+
const val KEY_MESSAGE_REASON = "KEY_MESSAGE_REASON"
12+
13+
1114
const val KEY_HEARTBEAT_ID = "KEY_HEARTBEAT_ID"
1215

1316
const val SIM1 = "SIM1"

android/app/src/main/java/com/httpsms/DeliveredReceiver.kt

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import android.app.Activity
44
import android.content.BroadcastReceiver
55
import android.content.Context
66
import android.content.Intent
7+
import androidx.work.Constraints
8+
import androidx.work.Data
9+
import androidx.work.NetworkType
10+
import androidx.work.OneTimeWorkRequest
11+
import androidx.work.WorkManager
12+
import androidx.work.Worker
13+
import androidx.work.WorkerParameters
14+
import androidx.work.workDataOf
715
import timber.log.Timber
8-
import java.time.ZoneOffset
9-
import java.time.ZonedDateTime
1016

1117

1218
internal class DeliveredReceiver : BroadcastReceiver() {
@@ -18,25 +24,87 @@ internal class DeliveredReceiver : BroadcastReceiver() {
1824
}
1925

2026
private fun handleMessageDelivered(context: Context, messageId: String?) {
21-
val timestamp = ZonedDateTime.now(ZoneOffset.UTC)
2227
if (!Receiver.isValid(context, messageId)) {
2328
return
2429
}
25-
Thread {
26-
Timber.i("delivered message with ID [${messageId}]")
27-
HttpSmsApiService.create(context).sendDeliveredEvent(messageId!!, timestamp)
28-
}.start()
30+
31+
val constraints = Constraints.Builder()
32+
.setRequiredNetworkType(NetworkType.CONNECTED)
33+
.build()
34+
35+
val inputData: Data = workDataOf(
36+
Constants.KEY_MESSAGE_ID to messageId,
37+
Constants.KEY_MESSAGE_TIMESTAMP to Settings.currentTimestamp()
38+
)
39+
40+
val work = OneTimeWorkRequest
41+
.Builder(DeliveredMessageWorker::class.java)
42+
.setConstraints(constraints)
43+
.setInputData(inputData)
44+
.build()
45+
46+
WorkManager
47+
.getInstance(context)
48+
.enqueue(work)
49+
50+
Timber.d("work enqueued with ID [${work.id}] for [DELIVERED] message with ID [${messageId}]")
2951
}
3052

3153
private fun handleMessageFailed(context: Context, messageId: String?) {
32-
val timestamp = ZonedDateTime.now(ZoneOffset.UTC)
3354
if (!Receiver.isValid(context, messageId)) {
3455
return
3556
}
3657

37-
Thread {
38-
Timber.i("message with ID [${messageId}] not delivered")
39-
HttpSmsApiService.create(context).sendFailedEvent(messageId!!,timestamp, "NOT_DELIVERED")
40-
}.start()
58+
val constraints = Constraints.Builder()
59+
.setRequiredNetworkType(NetworkType.CONNECTED)
60+
.build()
61+
62+
val inputData: Data = workDataOf(
63+
Constants.KEY_MESSAGE_ID to messageId,
64+
Constants.KEY_MESSAGE_REASON to "CANNOT BE DELIVERED",
65+
Constants.KEY_MESSAGE_TIMESTAMP to Settings.currentTimestamp()
66+
)
67+
68+
val work = OneTimeWorkRequest
69+
.Builder(FailedMessageWorker::class.java)
70+
.setConstraints(constraints)
71+
.setInputData(inputData)
72+
.build()
73+
74+
WorkManager
75+
.getInstance(context)
76+
.enqueue(work)
77+
78+
Timber.d("work enqueued with ID [${work.id}] for [FAILED] message with ID [${messageId}]")
79+
}
80+
81+
82+
internal class DeliveredMessageWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
83+
override fun doWork(): Result {
84+
val messageId = this.inputData.getString(Constants.KEY_MESSAGE_ID)
85+
val timestamp = this.inputData.getString(Constants.KEY_MESSAGE_TIMESTAMP)
86+
87+
Timber.i("[${timestamp}] sending [SENT] message event with ID [${messageId}]")
88+
89+
if (HttpSmsApiService.create(applicationContext).sendDeliveredEvent(messageId!!, timestamp!!)){
90+
return Result.success()
91+
}
92+
return Result.retry()
93+
}
94+
}
95+
96+
internal class FailedMessageWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
97+
override fun doWork(): Result {
98+
val messageId = this.inputData.getString(Constants.KEY_MESSAGE_ID)
99+
val reason = this.inputData.getString(Constants.KEY_MESSAGE_REASON)
100+
val timestamp = this.inputData.getString(Constants.KEY_MESSAGE_TIMESTAMP)
101+
102+
Timber.i("[${timestamp}] sending [FAILED] message event with ID [${messageId}] and reason [$reason]")
103+
104+
if (HttpSmsApiService.create(applicationContext).sendFailedEvent(messageId!!, timestamp!!, reason!!)){
105+
return Result.success()
106+
}
107+
return Result.retry()
108+
}
41109
}
42110
}

android/app/src/main/java/com/httpsms/FirebaseMessagingService.kt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,29 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
184184
}
185185

186186
private fun handleFailed(context: Context, messageID: String) {
187-
Timber.d("sending failed event for message with ID [${messageID}]")
188-
HttpSmsApiService.create(context)
189-
.sendFailedEvent(messageID, ZonedDateTime.now(ZoneOffset.UTC), "MOBILE_APP_INACTIVE")
187+
Timber.d("sending [FAILED] event for message with ID [${messageID}]")
188+
189+
val constraints = Constraints.Builder()
190+
.setRequiredNetworkType(NetworkType.CONNECTED)
191+
.build()
192+
193+
val inputData: Data = workDataOf(
194+
Constants.KEY_MESSAGE_ID to messageID,
195+
Constants.KEY_MESSAGE_REASON to "MOBILE_APP_INACTIVE",
196+
Constants.KEY_MESSAGE_TIMESTAMP to Settings.currentTimestamp()
197+
)
198+
199+
val work = OneTimeWorkRequest
200+
.Builder(SentReceiver.FailedMessageWorker::class.java)
201+
.setConstraints(constraints)
202+
.setInputData(inputData)
203+
.build()
204+
205+
WorkManager
206+
.getInstance(context)
207+
.enqueue(work)
208+
209+
Timber.d("work enqueued with ID [${work.id}] for [FAILED] message with ID [${messageID}]")
190210
}
191211

192212
private fun getMessage(context: Context, messageID: String): Message? {
@@ -237,7 +257,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
237257
this.applicationContext,
238258
id.hashCode(),
239259
intent,
240-
PendingIntent.FLAG_MUTABLE
260+
PendingIntent.FLAG_IMMUTABLE
241261
)
242262
}
243263
}

android/app/src/main/java/com/httpsms/HttpSmsApiService.kt

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,16 @@ class HttpSmsApiService(private val apiKey: String, private val baseURL: URI) {
5959
return null
6060
}
6161

62-
fun sendDeliveredEvent(messageId: String, timestamp: ZonedDateTime) {
63-
sendEvent(messageId, "DELIVERED", timestamp)
62+
fun sendDeliveredEvent(messageId: String, timestamp: String): Boolean {
63+
return sendEvent(messageId, "DELIVERED", timestamp)
6464
}
6565

66-
fun sendSentEvent(messageId: String, timestamp: ZonedDateTime) {
67-
sendEvent(messageId, "SENT", timestamp)
66+
fun sendSentEvent(messageId: String, timestamp: String): Boolean {
67+
return sendEvent(messageId, "SENT", timestamp)
6868
}
6969

70-
fun sendFailedEvent(messageId: String, timestamp: ZonedDateTime, reason: String) {
71-
sendEvent(messageId, "FAILED", timestamp, reason)
70+
fun sendFailedEvent(messageId: String, timestamp: String, reason: String): Boolean {
71+
return sendEvent(messageId, "FAILED", timestamp, reason)
7272
}
7373

7474
fun receive(sim: String, from: String, to: String, content: String, timestamp: String): Boolean {
@@ -129,10 +129,7 @@ class HttpSmsApiService(private val apiKey: String, private val baseURL: URI) {
129129
}
130130

131131

132-
private fun sendEvent(messageId: String, event: String, timestamp: ZonedDateTime, reason: String? = null) {
133-
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'000000'ZZZZZ")
134-
val timestampString = formatter.format(timestamp).replace("+", "Z")
135-
132+
private fun sendEvent(messageId: String, event: String, timestamp: String, reason: String? = null): Boolean {
136133
var reasonString = "null"
137134
if (reason != null) {
138135
reasonString = "\"$reason\""
@@ -142,7 +139,7 @@ class HttpSmsApiService(private val apiKey: String, private val baseURL: URI) {
142139
{
143140
"event_name": "$event",
144141
"reason": $reasonString,
145-
"timestamp": "$timestampString"
142+
"timestamp": "$timestamp"
146143
}
147144
""".trimIndent()
148145

@@ -157,11 +154,12 @@ class HttpSmsApiService(private val apiKey: String, private val baseURL: URI) {
157154
if (!response.isSuccessful) {
158155
Timber.e("error response [${response.body?.string()}] with code [${response.code}] while sending [${event}] event [${body}] for message with ID [${messageId}]")
159156
response.close()
160-
return
157+
return false
161158
}
162159

163160
response.close()
164161
Timber.i( "[$event] event sent successfully for message with ID [$messageId]" )
162+
return true
165163
}
166164

167165

android/app/src/main/java/com/httpsms/SentReceiver.kt

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@ import android.content.BroadcastReceiver
55
import android.content.Context
66
import android.content.Intent
77
import android.telephony.SmsManager
8+
import androidx.work.Constraints
9+
import androidx.work.Data
10+
import androidx.work.NetworkType
11+
import androidx.work.OneTimeWorkRequest
12+
import androidx.work.WorkManager
13+
import androidx.work.Worker
14+
import androidx.work.WorkerParameters
15+
import androidx.work.workDataOf
816
import timber.log.Timber
917
import java.time.ZoneOffset
1018
import java.time.ZonedDateTime
19+
import java.time.format.DateTimeFormatter
1120

1221
internal class SentReceiver : BroadcastReceiver() {
1322
override fun onReceive(context: Context, intent: Intent) {
@@ -22,26 +31,82 @@ internal class SentReceiver : BroadcastReceiver() {
2231
}
2332

2433
private fun handleMessageSent(context: Context, messageId: String?) {
25-
val timestamp = ZonedDateTime.now(ZoneOffset.UTC)
26-
if (!Receiver.isValid(context, messageId)) {
27-
return
28-
}
34+
val constraints = Constraints.Builder()
35+
.setRequiredNetworkType(NetworkType.CONNECTED)
36+
.build()
37+
38+
val inputData: Data = workDataOf(
39+
Constants.KEY_MESSAGE_ID to messageId,
40+
Constants.KEY_MESSAGE_TIMESTAMP to Settings.currentTimestamp()
41+
)
42+
43+
val work = OneTimeWorkRequest
44+
.Builder(SentMessageWorker::class.java)
45+
.setConstraints(constraints)
46+
.setInputData(inputData)
47+
.build()
2948

30-
Thread {
31-
Timber.d("sent message with ID [${messageId}]")
32-
HttpSmsApiService.create(context).sendSentEvent(messageId!!,timestamp)
33-
}.start()
49+
WorkManager
50+
.getInstance(context)
51+
.enqueue(work)
52+
53+
Timber.d("work enqueued with ID [${work.id}] for [SENT] message with ID [${messageId}]")
3454
}
3555

3656
private fun handleMessageFailed(context: Context, messageId: String?, reason: String) {
37-
val timestamp = ZonedDateTime.now(ZoneOffset.UTC)
3857
if (!Receiver.isValid(context, messageId)) {
3958
return
4059
}
4160

42-
Thread {
43-
Timber.i("message with ID [${messageId}] not sent with reason [$reason]")
44-
HttpSmsApiService.create(context).sendFailedEvent(messageId!!, timestamp, reason)
45-
}.start()
61+
val constraints = Constraints.Builder()
62+
.setRequiredNetworkType(NetworkType.CONNECTED)
63+
.build()
64+
65+
val inputData: Data = workDataOf(
66+
Constants.KEY_MESSAGE_ID to messageId,
67+
Constants.KEY_MESSAGE_REASON to reason,
68+
Constants.KEY_MESSAGE_TIMESTAMP to Settings.currentTimestamp()
69+
)
70+
71+
val work = OneTimeWorkRequest
72+
.Builder(FailedMessageWorker::class.java)
73+
.setConstraints(constraints)
74+
.setInputData(inputData)
75+
.build()
76+
77+
WorkManager
78+
.getInstance(context)
79+
.enqueue(work)
80+
81+
Timber.d("work enqueued with ID [${work.id}] for [FAILED] message with ID [${messageId}]")
82+
}
83+
84+
internal class SentMessageWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
85+
override fun doWork(): Result {
86+
val messageId = this.inputData.getString(Constants.KEY_MESSAGE_ID)
87+
val timestamp = this.inputData.getString(Constants.KEY_MESSAGE_TIMESTAMP)
88+
89+
Timber.i("[${timestamp}] sending [SENT] message event with ID [${messageId}]")
90+
91+
if (HttpSmsApiService.create(applicationContext).sendSentEvent(messageId!!, timestamp!!)){
92+
return Result.success()
93+
}
94+
return Result.retry()
95+
}
96+
}
97+
98+
internal class FailedMessageWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
99+
override fun doWork(): Result {
100+
val messageId = this.inputData.getString(Constants.KEY_MESSAGE_ID)
101+
val reason = this.inputData.getString(Constants.KEY_MESSAGE_REASON)
102+
val timestamp = this.inputData.getString(Constants.KEY_MESSAGE_TIMESTAMP)
103+
104+
Timber.i("[${timestamp}] sending [FAILED] message event with ID [${messageId}] and reason [$reason]")
105+
106+
if (HttpSmsApiService.create(applicationContext).sendFailedEvent(messageId!!, timestamp!!, reason!!)){
107+
return Result.success()
108+
}
109+
return Result.retry()
110+
}
46111
}
47112
}

android/app/src/main/java/com/httpsms/Settings.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import android.os.BatteryManager
55
import androidx.preference.PreferenceManager
66
import timber.log.Timber
77
import java.net.URI
8+
import java.time.ZoneOffset
9+
import java.time.ZonedDateTime
10+
import java.time.format.DateTimeFormatter
811

912
object Settings {
1013
private const val SETTINGS_SIM1_PHONE_NUMBER = "SETTINGS_SIM1_PHONE_NUMBER"
@@ -267,6 +270,12 @@ object Settings {
267270
return timestamp
268271
}
269272

273+
fun currentTimestamp(): String {
274+
return DateTimeFormatter.ofPattern(Constants.TIMESTAMP_PATTERN).format(
275+
ZonedDateTime.now(ZoneOffset.UTC)
276+
).replace("+", "Z")
277+
}
278+
270279

271280
fun setHeartbeatTimestampAsync(context: Context, timestamp: Long) {
272281
Timber.d(Settings::setHeartbeatTimestampAsync.name)

0 commit comments

Comments
 (0)