mirror of
https://github.com/vernu/textbee.git
synced 2026-09-03 03:29:58 +03:00
Merge pull request #199 from vernu/dev
better handling of failed sms error messaging
This commit is contained in:
@@ -199,9 +199,17 @@ public class TextBeeUtils {
|
||||
Log.d(TAG, "Could not get SIM slot index for subscription " + subscriptionInfo.getSubscriptionId());
|
||||
}
|
||||
|
||||
// Get MCC
|
||||
// Get MCC (getMccString() is API 29+; use getMcc() on older devices)
|
||||
try {
|
||||
String mcc = subscriptionInfo.getMccString();
|
||||
String mcc = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
mcc = subscriptionInfo.getMccString();
|
||||
} else {
|
||||
int mccInt = subscriptionInfo.getMcc();
|
||||
if (mccInt != Integer.MAX_VALUE) {
|
||||
mcc = String.format("%03d", mccInt);
|
||||
}
|
||||
}
|
||||
if (mcc != null && !mcc.isEmpty()) {
|
||||
simInfo.setMcc(mcc);
|
||||
}
|
||||
@@ -209,9 +217,17 @@ public class TextBeeUtils {
|
||||
Log.d(TAG, "Could not get MCC for subscription " + subscriptionInfo.getSubscriptionId());
|
||||
}
|
||||
|
||||
// Get MNC
|
||||
// Get MNC (getMncString() is API 29+; use getMnc() on older devices)
|
||||
try {
|
||||
String mnc = subscriptionInfo.getMncString();
|
||||
String mnc = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
mnc = subscriptionInfo.getMncString();
|
||||
} else {
|
||||
int mncInt = subscriptionInfo.getMnc();
|
||||
if (mncInt != Integer.MAX_VALUE) {
|
||||
mnc = String.valueOf(mncInt);
|
||||
}
|
||||
}
|
||||
if (mnc != null && !mnc.isEmpty()) {
|
||||
simInfo.setMnc(mnc);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import android.content.Intent;
|
||||
import android.telephony.SmsManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import com.vernu.sms.AppConstants;
|
||||
import com.vernu.sms.dtos.SMSDTO;
|
||||
import com.vernu.sms.helpers.SharedPreferenceHelper;
|
||||
@@ -19,6 +22,29 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
public static final String SMS_SENT = "SMS_SENT";
|
||||
public static final String SMS_DELIVERED = "SMS_DELIVERED";
|
||||
|
||||
/**
|
||||
* Resolves a result code to the constant name (e.g. SmsManager.RESULT_ERROR_GENERIC_FAILURE)
|
||||
* via reflection. Returns null if no matching constant is found.
|
||||
*/
|
||||
private static String getResultCodeName(int resultCode) {
|
||||
for (Class<?> clazz : new Class<?>[]{ SmsManager.class, Activity.class }) {
|
||||
try {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() != int.class) continue;
|
||||
if (!Modifier.isStatic(field.getModifiers()) || !Modifier.isFinal(field.getModifiers())) continue;
|
||||
if (!field.getName().startsWith("RESULT_")) continue;
|
||||
field.setAccessible(true);
|
||||
if (field.getInt(null) == resultCode) {
|
||||
return clazz.getSimpleName() + "." + field.getName();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Reflection failed for " + clazz.getSimpleName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String smsId = intent.getStringExtra("sms_id");
|
||||
@@ -30,13 +56,13 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
smsDTO.setSmsBatchId(smsBatchId);
|
||||
|
||||
if (SMS_SENT.equals(action)) {
|
||||
handleSentStatus(context, getResultCode(), smsDTO);
|
||||
handleSentStatus(context, intent, getResultCode(), smsDTO);
|
||||
} else if (SMS_DELIVERED.equals(action)) {
|
||||
handleDeliveredStatus(context, getResultCode(), smsDTO);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSentStatus(Context context, int resultCode, SMSDTO smsDTO) {
|
||||
private void handleSentStatus(Context context, Intent intent, int resultCode, SMSDTO smsDTO) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String errorMessage = "";
|
||||
|
||||
@@ -47,7 +73,11 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.d(TAG, "SMS sent successfully - ID: " + smsDTO.getSmsId());
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
|
||||
errorMessage = "Generic failure";
|
||||
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.";
|
||||
int radioCode = intent.getIntExtra("errorCode", -1);
|
||||
if (radioCode != -1) {
|
||||
errorMessage += " (code " + radioCode + ")";
|
||||
}
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
@@ -55,7 +85,7 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_RADIO_OFF:
|
||||
errorMessage = "Radio off";
|
||||
errorMessage = "Mobile radio is off (e.g. airplane mode). Turn off airplane mode and ensure cellular is on.";
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
@@ -63,7 +93,7 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_NULL_PDU:
|
||||
errorMessage = "Null PDU";
|
||||
errorMessage = "Message could not be sent; invalid format or carrier issue. Try a shorter message or different recipient.";
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
@@ -71,7 +101,7 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_NO_SERVICE:
|
||||
errorMessage = "No service";
|
||||
errorMessage = "No cellular service. Check signal and try again when you have coverage.";
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
@@ -79,7 +109,7 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED:
|
||||
errorMessage = "Sending limit exceeded";
|
||||
errorMessage = "Device/carrier send limit reached (too many SMS in a short time). Wait a few minutes or lower the send rate.";
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
@@ -87,7 +117,7 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED:
|
||||
errorMessage = "Short code not allowed";
|
||||
errorMessage = "Short code not allowed on this carrier. Use a full phone number.";
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
@@ -95,7 +125,7 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED:
|
||||
errorMessage = "Short code never allowed";
|
||||
errorMessage = "Short codes are not supported on this carrier. Use a full phone number.";
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
@@ -103,7 +133,7 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
case SmsManager.RESULT_NETWORK_ERROR:
|
||||
errorMessage = "Network error";
|
||||
errorMessage = "Network error while sending. Check signal and try again.";
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
@@ -111,12 +141,13 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
default:
|
||||
errorMessage = "Unknown error";
|
||||
String codeName = getResultCodeName(resultCode);
|
||||
errorMessage = codeName != null ? codeName : ("Unknown error (code " + resultCode + ")");
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
smsDTO.setErrorMessage(errorMessage);
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Unknown error code: " + resultCode);
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error: " + errorMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -134,18 +165,19 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
Log.d(TAG, "SMS delivered successfully - ID: " + smsDTO.getSmsId());
|
||||
break;
|
||||
case Activity.RESULT_CANCELED:
|
||||
errorMessage = "Delivery canceled";
|
||||
errorMessage = "Delivery report was canceled (e.g. carrier does not support delivery receipts). Message may still have been delivered.";
|
||||
smsDTO.setStatus("DELIVERY_FAILED");
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
smsDTO.setErrorMessage(errorMessage);
|
||||
Log.e(TAG, "SMS delivery failed - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
default:
|
||||
errorMessage = "Unknown delivery error";
|
||||
String deliveryCodeName = getResultCodeName(resultCode);
|
||||
errorMessage = deliveryCodeName != null ? deliveryCodeName : ("Unknown delivery error (code " + resultCode + ")");
|
||||
smsDTO.setStatus("DELIVERY_FAILED");
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
smsDTO.setErrorMessage(errorMessage);
|
||||
Log.e(TAG, "SMS delivery failed - ID: " + smsDTO.getSmsId() + ", Unknown error code: " + resultCode);
|
||||
Log.e(TAG, "SMS delivery failed - ID: " + smsDTO.getSmsId() + ", Error: " + errorMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.vernu.sms.services;
|
||||
|
||||
import android.app.ForegroundServiceStartNotAllowedException;
|
||||
import android.app.*;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -42,8 +43,14 @@ public class StickyNotificationService extends Service {
|
||||
|
||||
if (stickyNotificationEnabled) {
|
||||
Notification notification = createNotification();
|
||||
startForeground(1, notification);
|
||||
Log.i(TAG, "Started foreground service with sticky notification");
|
||||
try {
|
||||
startForeground(1, notification);
|
||||
Log.i(TAG, "Started foreground service with sticky notification");
|
||||
} catch (ForegroundServiceStartNotAllowedException e) {
|
||||
Log.w(TAG, "Cannot start foreground from background, stopping service: " + e.getMessage());
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Sticky notification disabled by user preference");
|
||||
}
|
||||
|
||||
@@ -48,6 +48,30 @@ export class SmsQueueProcessor {
|
||||
`SMS Job ${job.id} completed, success: ${response.successCount}, failures: ${response.failureCount}`,
|
||||
)
|
||||
|
||||
// Mark individual SMS records as failed when their FCM push failed
|
||||
for (let i = 0; i < response.responses.length; i++) {
|
||||
if (!response.responses[i].success) {
|
||||
try {
|
||||
const smsData = JSON.parse(fcmMessages[i].data.smsData)
|
||||
await this.smsModel.findByIdAndUpdate(smsData.smsId, {
|
||||
$set: {
|
||||
status: 'failed',
|
||||
failedAt: new Date(),
|
||||
errorCode: 'FCM_DELIVERY_FAILED',
|
||||
errorMessage:
|
||||
response.responses[i].error?.message ||
|
||||
'FCM push notification delivery failed',
|
||||
},
|
||||
})
|
||||
} catch (parseError) {
|
||||
this.logger.error(
|
||||
`Failed to mark SMS as failed for FCM message index ${i}`,
|
||||
parseError,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update device SMS count
|
||||
await this.deviceModel
|
||||
.findByIdAndUpdate(deviceId, {
|
||||
@@ -77,6 +101,26 @@ export class SmsQueueProcessor {
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to process SMS job ${job.id}`, error)
|
||||
|
||||
// Mark all individual SMS in this batch of FCM messages as failed
|
||||
for (const fcmMessage of fcmMessages) {
|
||||
try {
|
||||
const smsData = JSON.parse(fcmMessage.data.smsData)
|
||||
await this.smsModel.findByIdAndUpdate(smsData.smsId, {
|
||||
$set: {
|
||||
status: 'failed',
|
||||
failedAt: new Date(),
|
||||
errorCode: 'FCM_SEND_ERROR',
|
||||
errorMessage: error?.message || 'FCM sendEach call failed',
|
||||
},
|
||||
})
|
||||
} catch (parseError) {
|
||||
this.logger.error(
|
||||
'Failed to mark SMS as failed after FCM error',
|
||||
parseError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const smsBatch = await this.smsBatchModel.findByIdAndUpdate(
|
||||
smsBatchId,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user