Skip to content

Commit 5ae010f

Browse files
authored
Merge pull request #199 from vernu/dev
better handling of failed sms error messaging
2 parents 17351e2 + 94a0cd8 commit 5ae010f

File tree

4 files changed

+120
-21
lines changed

4 files changed

+120
-21
lines changed

android/app/src/main/java/com/vernu/sms/TextBeeUtils.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,19 +199,35 @@ public static List<SimInfoDTO> collectSimInfo(Context context) {
199199
Log.d(TAG, "Could not get SIM slot index for subscription " + subscriptionInfo.getSubscriptionId());
200200
}
201201

202-
// Get MCC
202+
// Get MCC (getMccString() is API 29+; use getMcc() on older devices)
203203
try {
204-
String mcc = subscriptionInfo.getMccString();
204+
String mcc = null;
205+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
206+
mcc = subscriptionInfo.getMccString();
207+
} else {
208+
int mccInt = subscriptionInfo.getMcc();
209+
if (mccInt != Integer.MAX_VALUE) {
210+
mcc = String.format("%03d", mccInt);
211+
}
212+
}
205213
if (mcc != null && !mcc.isEmpty()) {
206214
simInfo.setMcc(mcc);
207215
}
208216
} catch (Exception e) {
209217
Log.d(TAG, "Could not get MCC for subscription " + subscriptionInfo.getSubscriptionId());
210218
}
211219

212-
// Get MNC
220+
// Get MNC (getMncString() is API 29+; use getMnc() on older devices)
213221
try {
214-
String mnc = subscriptionInfo.getMncString();
222+
String mnc = null;
223+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
224+
mnc = subscriptionInfo.getMncString();
225+
} else {
226+
int mncInt = subscriptionInfo.getMnc();
227+
if (mncInt != Integer.MAX_VALUE) {
228+
mnc = String.valueOf(mncInt);
229+
}
230+
}
215231
if (mnc != null && !mnc.isEmpty()) {
216232
simInfo.setMnc(mnc);
217233
}

android/app/src/main/java/com/vernu/sms/receivers/SMSStatusReceiver.java

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import android.telephony.SmsManager;
88
import android.util.Log;
99

10+
import java.lang.reflect.Field;
11+
import java.lang.reflect.Modifier;
12+
1013
import com.vernu.sms.AppConstants;
1114
import com.vernu.sms.dtos.SMSDTO;
1215
import com.vernu.sms.helpers.SharedPreferenceHelper;
@@ -19,6 +22,29 @@ public class SMSStatusReceiver extends BroadcastReceiver {
1922
public static final String SMS_SENT = "SMS_SENT";
2023
public static final String SMS_DELIVERED = "SMS_DELIVERED";
2124

25+
/**
26+
* Resolves a result code to the constant name (e.g. SmsManager.RESULT_ERROR_GENERIC_FAILURE)
27+
* via reflection. Returns null if no matching constant is found.
28+
*/
29+
private static String getResultCodeName(int resultCode) {
30+
for (Class<?> clazz : new Class<?>[]{ SmsManager.class, Activity.class }) {
31+
try {
32+
for (Field field : clazz.getDeclaredFields()) {
33+
if (field.getType() != int.class) continue;
34+
if (!Modifier.isStatic(field.getModifiers()) || !Modifier.isFinal(field.getModifiers())) continue;
35+
if (!field.getName().startsWith("RESULT_")) continue;
36+
field.setAccessible(true);
37+
if (field.getInt(null) == resultCode) {
38+
return clazz.getSimpleName() + "." + field.getName();
39+
}
40+
}
41+
} catch (Exception e) {
42+
Log.w(TAG, "Reflection failed for " + clazz.getSimpleName() + ": " + e.getMessage());
43+
}
44+
}
45+
return null;
46+
}
47+
2248
@Override
2349
public void onReceive(Context context, Intent intent) {
2450
String smsId = intent.getStringExtra("sms_id");
@@ -30,13 +56,13 @@ public void onReceive(Context context, Intent intent) {
3056
smsDTO.setSmsBatchId(smsBatchId);
3157

3258
if (SMS_SENT.equals(action)) {
33-
handleSentStatus(context, getResultCode(), smsDTO);
59+
handleSentStatus(context, intent, getResultCode(), smsDTO);
3460
} else if (SMS_DELIVERED.equals(action)) {
3561
handleDeliveredStatus(context, getResultCode(), smsDTO);
3662
}
3763
}
3864

39-
private void handleSentStatus(Context context, int resultCode, SMSDTO smsDTO) {
65+
private void handleSentStatus(Context context, Intent intent, int resultCode, SMSDTO smsDTO) {
4066
long timestamp = System.currentTimeMillis();
4167
String errorMessage = "";
4268

@@ -47,76 +73,81 @@ private void handleSentStatus(Context context, int resultCode, SMSDTO smsDTO) {
4773
Log.d(TAG, "SMS sent successfully - ID: " + smsDTO.getSmsId());
4874
break;
4975
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
50-
errorMessage = "Generic failure";
76+
errorMessage = "SMS failed on device. Common causes: no SMS credit on SIM, weak signal, or carrier blocked. Check SIM balance and signal, then try again.";
77+
int radioCode = intent.getIntExtra("errorCode", -1);
78+
if (radioCode != -1) {
79+
errorMessage += " (code " + radioCode + ")";
80+
}
5181
smsDTO.setStatus("FAILED");
5282
smsDTO.setFailedAtInMillis(timestamp);
5383
smsDTO.setErrorCode(String.valueOf(resultCode));
5484
smsDTO.setErrorMessage(errorMessage);
5585
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
5686
break;
5787
case SmsManager.RESULT_ERROR_RADIO_OFF:
58-
errorMessage = "Radio off";
88+
errorMessage = "Mobile radio is off (e.g. airplane mode). Turn off airplane mode and ensure cellular is on.";
5989
smsDTO.setStatus("FAILED");
6090
smsDTO.setFailedAtInMillis(timestamp);
6191
smsDTO.setErrorCode(String.valueOf(resultCode));
6292
smsDTO.setErrorMessage(errorMessage);
6393
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
6494
break;
6595
case SmsManager.RESULT_ERROR_NULL_PDU:
66-
errorMessage = "Null PDU";
96+
errorMessage = "Message could not be sent; invalid format or carrier issue. Try a shorter message or different recipient.";
6797
smsDTO.setStatus("FAILED");
6898
smsDTO.setFailedAtInMillis(timestamp);
6999
smsDTO.setErrorCode(String.valueOf(resultCode));
70100
smsDTO.setErrorMessage(errorMessage);
71101
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
72102
break;
73103
case SmsManager.RESULT_ERROR_NO_SERVICE:
74-
errorMessage = "No service";
104+
errorMessage = "No cellular service. Check signal and try again when you have coverage.";
75105
smsDTO.setStatus("FAILED");
76106
smsDTO.setFailedAtInMillis(timestamp);
77107
smsDTO.setErrorCode(String.valueOf(resultCode));
78108
smsDTO.setErrorMessage(errorMessage);
79109
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
80110
break;
81111
case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED:
82-
errorMessage = "Sending limit exceeded";
112+
errorMessage = "Device/carrier send limit reached (too many SMS in a short time). Wait a few minutes or lower the send rate.";
83113
smsDTO.setStatus("FAILED");
84114
smsDTO.setFailedAtInMillis(timestamp);
85115
smsDTO.setErrorCode(String.valueOf(resultCode));
86116
smsDTO.setErrorMessage(errorMessage);
87117
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
88118
break;
89119
case SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED:
90-
errorMessage = "Short code not allowed";
120+
errorMessage = "Short code not allowed on this carrier. Use a full phone number.";
91121
smsDTO.setStatus("FAILED");
92122
smsDTO.setFailedAtInMillis(timestamp);
93123
smsDTO.setErrorCode(String.valueOf(resultCode));
94124
smsDTO.setErrorMessage(errorMessage);
95125
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
96126
break;
97127
case SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED:
98-
errorMessage = "Short code never allowed";
128+
errorMessage = "Short codes are not supported on this carrier. Use a full phone number.";
99129
smsDTO.setStatus("FAILED");
100130
smsDTO.setFailedAtInMillis(timestamp);
101131
smsDTO.setErrorCode(String.valueOf(resultCode));
102132
smsDTO.setErrorMessage(errorMessage);
103133
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
104134
break;
105135
case SmsManager.RESULT_NETWORK_ERROR:
106-
errorMessage = "Network error";
136+
errorMessage = "Network error while sending. Check signal and try again.";
107137
smsDTO.setStatus("FAILED");
108138
smsDTO.setFailedAtInMillis(timestamp);
109139
smsDTO.setErrorCode(String.valueOf(resultCode));
110140
smsDTO.setErrorMessage(errorMessage);
111141
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
112142
break;
113143
default:
114-
errorMessage = "Unknown error";
144+
String codeName = getResultCodeName(resultCode);
145+
errorMessage = codeName != null ? codeName : ("Unknown error (code " + resultCode + ")");
115146
smsDTO.setStatus("FAILED");
116147
smsDTO.setFailedAtInMillis(timestamp);
117148
smsDTO.setErrorCode(String.valueOf(resultCode));
118149
smsDTO.setErrorMessage(errorMessage);
119-
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Unknown error code: " + resultCode);
150+
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error: " + errorMessage);
120151
break;
121152
}
122153

@@ -134,18 +165,19 @@ private void handleDeliveredStatus(Context context, int resultCode, SMSDTO smsDT
134165
Log.d(TAG, "SMS delivered successfully - ID: " + smsDTO.getSmsId());
135166
break;
136167
case Activity.RESULT_CANCELED:
137-
errorMessage = "Delivery canceled";
168+
errorMessage = "Delivery report was canceled (e.g. carrier does not support delivery receipts). Message may still have been delivered.";
138169
smsDTO.setStatus("DELIVERY_FAILED");
139170
smsDTO.setErrorCode(String.valueOf(resultCode));
140171
smsDTO.setErrorMessage(errorMessage);
141172
Log.e(TAG, "SMS delivery failed - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
142173
break;
143174
default:
144-
errorMessage = "Unknown delivery error";
175+
String deliveryCodeName = getResultCodeName(resultCode);
176+
errorMessage = deliveryCodeName != null ? deliveryCodeName : ("Unknown delivery error (code " + resultCode + ")");
145177
smsDTO.setStatus("DELIVERY_FAILED");
146178
smsDTO.setErrorCode(String.valueOf(resultCode));
147179
smsDTO.setErrorMessage(errorMessage);
148-
Log.e(TAG, "SMS delivery failed - ID: " + smsDTO.getSmsId() + ", Unknown error code: " + resultCode);
180+
Log.e(TAG, "SMS delivery failed - ID: " + smsDTO.getSmsId() + ", Error: " + errorMessage);
149181
break;
150182
}
151183

android/app/src/main/java/com/vernu/sms/services/StickyNotificationService.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.vernu.sms.services;
22

3+
import android.app.ForegroundServiceStartNotAllowedException;
34
import android.app.*;
45
import android.content.BroadcastReceiver;
56
import android.content.Context;
@@ -42,8 +43,14 @@ public void onCreate() {
4243

4344
if (stickyNotificationEnabled) {
4445
Notification notification = createNotification();
45-
startForeground(1, notification);
46-
Log.i(TAG, "Started foreground service with sticky notification");
46+
try {
47+
startForeground(1, notification);
48+
Log.i(TAG, "Started foreground service with sticky notification");
49+
} catch (ForegroundServiceStartNotAllowedException e) {
50+
Log.w(TAG, "Cannot start foreground from background, stopping service: " + e.getMessage());
51+
stopSelf();
52+
return;
53+
}
4754
} else {
4855
Log.i(TAG, "Sticky notification disabled by user preference");
4956
}

api/src/gateway/queue/sms-queue.processor.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,30 @@ export class SmsQueueProcessor {
4848
`SMS Job ${job.id} completed, success: ${response.successCount}, failures: ${response.failureCount}`,
4949
)
5050

51+
// Mark individual SMS records as failed when their FCM push failed
52+
for (let i = 0; i < response.responses.length; i++) {
53+
if (!response.responses[i].success) {
54+
try {
55+
const smsData = JSON.parse(fcmMessages[i].data.smsData)
56+
await this.smsModel.findByIdAndUpdate(smsData.smsId, {
57+
$set: {
58+
status: 'failed',
59+
failedAt: new Date(),
60+
errorCode: 'FCM_DELIVERY_FAILED',
61+
errorMessage:
62+
response.responses[i].error?.message ||
63+
'FCM push notification delivery failed',
64+
},
65+
})
66+
} catch (parseError) {
67+
this.logger.error(
68+
`Failed to mark SMS as failed for FCM message index ${i}`,
69+
parseError,
70+
)
71+
}
72+
}
73+
}
74+
5175
// Update device SMS count
5276
await this.deviceModel
5377
.findByIdAndUpdate(deviceId, {
@@ -77,6 +101,26 @@ export class SmsQueueProcessor {
77101
} catch (error) {
78102
this.logger.error(`Failed to process SMS job ${job.id}`, error)
79103

104+
// Mark all individual SMS in this batch of FCM messages as failed
105+
for (const fcmMessage of fcmMessages) {
106+
try {
107+
const smsData = JSON.parse(fcmMessage.data.smsData)
108+
await this.smsModel.findByIdAndUpdate(smsData.smsId, {
109+
$set: {
110+
status: 'failed',
111+
failedAt: new Date(),
112+
errorCode: 'FCM_SEND_ERROR',
113+
errorMessage: error?.message || 'FCM sendEach call failed',
114+
},
115+
})
116+
} catch (parseError) {
117+
this.logger.error(
118+
'Failed to mark SMS as failed after FCM error',
119+
parseError,
120+
)
121+
}
122+
}
123+
80124
const smsBatch = await this.smsBatchModel.findByIdAndUpdate(
81125
smsBatchId,
82126
{

0 commit comments

Comments
 (0)