@@ -4,7 +4,7 @@ import android.content.BroadcastReceiver
44import android.content.Context
55import android.content.Intent
66import android.provider.Telephony
7- import androidx.work.BackoffPolicy
7+ import android.util.Base64
88import androidx.work.Constraints
99import androidx.work.Data
1010import androidx.work.NetworkType
@@ -13,20 +13,30 @@ import androidx.work.WorkManager
1313import androidx.work.Worker
1414import androidx.work.WorkerParameters
1515import androidx.work.workDataOf
16+ import com.google.android.mms.pdu_alt.CharacterSets
17+ import com.google.android.mms.pdu_alt.MultimediaMessagePdu
18+ import com.google.android.mms.pdu_alt.PduParser
19+ import com.google.android.mms.pdu_alt.RetrieveConf
1620import timber.log.Timber
21+ import java.io.File
22+ import java.io.FileOutputStream
1723import java.time.ZoneOffset
1824import java.time.ZonedDateTime
1925import java.time.format.DateTimeFormatter
20- import java.util.concurrent.TimeUnit
2126
2227class ReceivedReceiver : BroadcastReceiver ()
2328{
24- override fun onReceive (context : Context ,intent : Intent ) {
25- if (intent.action != Telephony .Sms .Intents .SMS_RECEIVED_ACTION ) {
29+ override fun onReceive (context : Context , intent : Intent ) {
30+ if (intent.action == Telephony .Sms .Intents .SMS_RECEIVED_ACTION ) {
31+ handleSmsReceived(context, intent)
32+ } else if (intent.action == Telephony .Sms .Intents .WAP_PUSH_RECEIVED_ACTION ) {
33+ handleMmsReceived(context, intent)
34+ } else {
2635 Timber .e(" received invalid intent with action [${intent.action} ]" )
27- return
2836 }
37+ }
2938
39+ private fun handleSmsReceived (context : Context , intent : Intent ) {
3040 var smsSender = " "
3141 var smsBody = " "
3242
@@ -35,12 +45,7 @@ class ReceivedReceiver: BroadcastReceiver()
3545 smsBody + = smsMessage.messageBody
3646 }
3747
38- var sim = Constants .SIM1
39- var owner = Settings .getSIM1PhoneNumber(context)
40- if (intent.getIntExtra(" android.telephony.extra.SLOT_INDEX" , 0 ) > 0 && Settings .isDualSIM(context)) {
41- owner = Settings .getSIM2PhoneNumber(context)
42- sim = Constants .SIM2
43- }
48+ val (sim, owner) = getSimAndOwner(context, intent)
4449
4550 if (! Settings .isIncomingMessageEnabled(context, sim)) {
4651 Timber .w(" [${sim} ] is not active for incoming messages" )
@@ -56,7 +61,71 @@ class ReceivedReceiver: BroadcastReceiver()
5661 )
5762 }
5863
59- private fun handleMessageReceived (context : Context , sim : String , from : String , to : String , content : String ) {
64+ private fun handleMmsReceived (context : Context , intent : Intent ) {
65+ val pushData = intent.getByteArrayExtra(" data" ) ? : return
66+ val pdu = PduParser (pushData, true ).parse() ? : return
67+
68+ if (pdu !is MultimediaMessagePdu ) {
69+ Timber .d(" Received PDU is not a MultimediaMessagePdu, ignoring." )
70+ return
71+ }
72+
73+ val from = pdu.from?.string ? : " "
74+ var content = " "
75+ val attachmentFiles = mutableListOf<String >()
76+
77+ // Check if it's a RetrieveConf (which contains the actual message body)
78+ if (pdu is RetrieveConf ) {
79+ val body = pdu.body
80+ if (body != null ) {
81+ for (i in 0 until body.partsNum) {
82+ val part = body.getPart(i)
83+ val partData = part.data ? : continue
84+ val contentType = String (part.contentType ? : " application/octet-stream" .toByteArray())
85+
86+ if (contentType.startsWith(" text/plain" )) {
87+ content + = String (partData, charset(CharacterSets .getMimeName(part.charset)))
88+ } else {
89+ // Save attachment to a temporary file
90+ val fileName = String (part.name ? : part.contentLocation ? : part.contentId ? : " attachment_$i " .toByteArray())
91+ val tempFile = File (context.cacheDir, " received_mms_${System .currentTimeMillis()} _$i " )
92+ FileOutputStream (tempFile).use { it.write(partData) }
93+ attachmentFiles.add(" ${tempFile.absolutePath} |${contentType} |${fileName} " )
94+ }
95+ }
96+ }
97+ } else {
98+ Timber .d(" Received PDU is of type [${pdu.javaClass.simpleName} ], body extraction not implemented." )
99+ }
100+
101+ val (sim, owner) = getSimAndOwner(context, intent)
102+
103+ if (! Settings .isIncomingMessageEnabled(context, sim)) {
104+ Timber .w(" [${sim} ] is not active for incoming messages" )
105+ return
106+ }
107+
108+ handleMessageReceived(
109+ context,
110+ sim,
111+ from,
112+ owner,
113+ content,
114+ attachmentFiles.toTypedArray()
115+ )
116+ }
117+
118+ private fun getSimAndOwner (context : Context , intent : Intent ): Pair <String , String > {
119+ var sim = Constants .SIM1
120+ var owner = Settings .getSIM1PhoneNumber(context)
121+ if (intent.getIntExtra(" android.telephony.extra.SLOT_INDEX" , 0 ) > 0 && Settings .isDualSIM(context)) {
122+ owner = Settings .getSIM2PhoneNumber(context)
123+ sim = Constants .SIM2
124+ }
125+ return Pair (sim, owner)
126+ }
127+
128+ private fun handleMessageReceived (context : Context , sim : String , from : String , to : String , content : String , attachments : Array <String >? = null) {
60129 val timestamp = ZonedDateTime .now(ZoneOffset .UTC )
61130
62131 if (! Settings .isLoggedIn(context)) {
@@ -84,7 +153,8 @@ class ReceivedReceiver: BroadcastReceiver()
84153 Constants .KEY_MESSAGE_SIM to sim,
85154 Constants .KEY_MESSAGE_CONTENT to body,
86155 Constants .KEY_MESSAGE_ENCRYPTED to Settings .encryptReceivedMessages(context),
87- Constants .KEY_MESSAGE_TIMESTAMP to DateTimeFormatter .ofPattern(Constants .TIMESTAMP_PATTERN ).format(timestamp).replace(" +" , " Z" )
156+ Constants .KEY_MESSAGE_TIMESTAMP to DateTimeFormatter .ofPattern(Constants .TIMESTAMP_PATTERN ).format(timestamp).replace(" +" , " Z" ),
157+ Constants .KEY_MESSAGE_ATTACHMENTS to attachments
88158 )
89159
90160 val work = OneTimeWorkRequest
@@ -104,14 +174,52 @@ class ReceivedReceiver: BroadcastReceiver()
104174 override fun doWork (): Result {
105175 Timber .i(" [${this .inputData.getString(Constants .KEY_MESSAGE_SIM )} ] forwarding received message from [${this .inputData.getString(Constants .KEY_MESSAGE_FROM )} ] to [${this .inputData.getString(Constants .KEY_MESSAGE_TO )} ]" )
106176
107- if (HttpSmsApiService .create(applicationContext).receive(
108- this .inputData.getString(Constants .KEY_MESSAGE_SIM )!! ,
109- this .inputData.getString(Constants .KEY_MESSAGE_FROM )!! ,
110- this .inputData.getString(Constants .KEY_MESSAGE_TO )!! ,
111- this .inputData.getString(Constants .KEY_MESSAGE_CONTENT )!! ,
112- this .inputData.getBoolean(Constants .KEY_MESSAGE_ENCRYPTED , false ),
113- this .inputData.getString(Constants .KEY_MESSAGE_TIMESTAMP )!! ,
114- )) {
177+ val sim = this .inputData.getString(Constants .KEY_MESSAGE_SIM )!!
178+ val from = this .inputData.getString(Constants .KEY_MESSAGE_FROM )!!
179+ val to = this .inputData.getString(Constants .KEY_MESSAGE_TO )!!
180+ val content = this .inputData.getString(Constants .KEY_MESSAGE_CONTENT )!!
181+ val encrypted = this .inputData.getBoolean(Constants .KEY_MESSAGE_ENCRYPTED , false )
182+ val timestamp = this .inputData.getString(Constants .KEY_MESSAGE_TIMESTAMP )!!
183+
184+ val attachmentsData = inputData.getStringArray(Constants .KEY_MESSAGE_ATTACHMENTS )
185+ val attachments = attachmentsData?.mapNotNull {
186+ val parts = it.split(" |" )
187+ val file = File (parts[0 ])
188+ if (file.exists()) {
189+ val bytes = file.readBytes()
190+ val base64Content = Base64 .encodeToString(bytes, Base64 .NO_WRAP )
191+ ReceivedAttachment (
192+ name = parts[2 ],
193+ contentType = parts[1 ],
194+ content = base64Content
195+ )
196+ } else {
197+ null
198+ }
199+ }
200+
201+ val request = ReceivedMessageRequest (
202+ sim = sim,
203+ from = from,
204+ to = to,
205+ content = content,
206+ encrypted = encrypted,
207+ timestamp = timestamp,
208+ attachments = attachments
209+ )
210+
211+ val success = HttpSmsApiService .create(applicationContext).receive(request)
212+
213+ // Cleanup temp files
214+ attachmentsData?.forEach {
215+ val path = it.split(" |" )[0 ]
216+ val file = File (path)
217+ if (file.exists()) {
218+ file.delete()
219+ }
220+ }
221+
222+ if (success) {
115223 return Result .success()
116224 }
117225
0 commit comments