From 6886dd2f1a88ab578fdad569cc69e6b1697b646c Mon Sep 17 00:00:00 2001 From: isra el Date: Mon, 16 Jun 2025 08:12:15 +0300 Subject: [PATCH] feat(android): improve received sms and status tracking reliability --- android/app/build.gradle | 2 + android/app/src/main/AndroidManifest.xml | 8 ++ .../com/vernu/sms/SMSGatewayApplication.java | 20 ++++ .../sms/receivers/SMSBroadcastReceiver.java | 33 +----- .../sms/receivers/SMSStatusReceiver.java | 28 +---- .../vernu/sms/workers/SMSReceivedWorker.java | 106 ++++++++++++++++++ .../sms/workers/SMSStatusUpdateWorker.java | 105 +++++++++++++++++ 7 files changed, 246 insertions(+), 56 deletions(-) create mode 100644 android/app/src/main/java/com/vernu/sms/SMSGatewayApplication.java create mode 100644 android/app/src/main/java/com/vernu/sms/workers/SMSReceivedWorker.java create mode 100644 android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.java diff --git a/android/app/build.gradle b/android/app/build.gradle index f278bf0..915147f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -54,6 +54,8 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.journeyapps:zxing-android-embedded:4.1.0' + + implementation 'androidx.work:work-runtime:2.7.1' // def room_version = "2.4.2" // implementation "androidx.room:room-runtime:$room_version" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4d50cc5..2f0e761 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,7 +14,9 @@ + + + + apiCall = ApiManager.getApiService().sendReceivedSMS(deviceId, apiKey, receivedSMSDTO); - apiCall.enqueue(new retrofit2.Callback() { - @Override - public void onResponse(Call call, Response response) { -// Date now = new Date(); - if (response.isSuccessful()) { - Log.d(TAG, "SMS sent to server successfully"); -// receivedSMS.setLastAcknowledgedRequestAt(now); -// receivedSMS.setServerAcknowledgedAt(now); -// updateLocalReceivedSMS(receivedSMS, context); - } else { - Log.e(TAG, "Failed to send SMS to server"); -// receivedSMS.setServerAcknowledgedAt(null); -// receivedSMS.setLastAcknowledgedRequestAt(now); -// receivedSMS.setRetryCount(localReceivedSMS.getRetryCount() + 1); -// updateLocalReceivedSMS(receivedSMS, context); - } - } - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "Failed to send SMS to server", t); -// receivedSMS.setServerAcknowledgedAt(null); -// receivedSMS.setLastAcknowledgedRequestAt(new Date()); -// updateLocalReceivedSMS(receivedSMS, context); - } - }); + SMSReceivedWorker.enqueueWork(context, deviceId, apiKey, receivedSMSDTO); } // private void updateLocalReceivedSMS(SMS localReceivedSMS, Context context) { diff --git a/android/app/src/main/java/com/vernu/sms/receivers/SMSStatusReceiver.java b/android/app/src/main/java/com/vernu/sms/receivers/SMSStatusReceiver.java index 15e9c45..e50e7a4 100644 --- a/android/app/src/main/java/com/vernu/sms/receivers/SMSStatusReceiver.java +++ b/android/app/src/main/java/com/vernu/sms/receivers/SMSStatusReceiver.java @@ -7,16 +7,11 @@ import android.content.Intent; import android.telephony.SmsManager; import android.util.Log; -import com.vernu.sms.ApiManager; import com.vernu.sms.AppConstants; import com.vernu.sms.dtos.SMSDTO; -import com.vernu.sms.dtos.SMSForwardResponseDTO; import com.vernu.sms.helpers.SharedPreferenceHelper; -import com.vernu.sms.services.GatewayApiService; +import com.vernu.sms.workers.SMSStatusUpdateWorker; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; public class SMSStatusReceiver extends BroadcastReceiver { private static final String TAG = "SMSStatusReceiver"; @@ -157,24 +152,7 @@ public class SMSStatusReceiver extends BroadcastReceiver { Log.e(TAG, "Device ID or API key not found"); return; } - - GatewayApiService apiService = ApiManager.getApiService(); - Call call = apiService.updateSMSStatus(deviceId, apiKey, smsDTO); - - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.d(TAG, "SMS status updated successfully - ID: " + smsDTO.getSmsId() + ", Status: " + smsDTO.getStatus()); - } else { - Log.e(TAG, "Failed to update SMS status. Response code: " + response.code()); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "API call failed: " + t.getMessage()); - } - }); + + SMSStatusUpdateWorker.enqueueWork(context, deviceId, apiKey, smsDTO); } } \ No newline at end of file diff --git a/android/app/src/main/java/com/vernu/sms/workers/SMSReceivedWorker.java b/android/app/src/main/java/com/vernu/sms/workers/SMSReceivedWorker.java new file mode 100644 index 0000000..51c8c38 --- /dev/null +++ b/android/app/src/main/java/com/vernu/sms/workers/SMSReceivedWorker.java @@ -0,0 +1,106 @@ +package com.vernu.sms.workers; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.Data; +import androidx.work.Worker; +import androidx.work.WorkerParameters; +import androidx.work.BackoffPolicy; +import androidx.work.Constraints; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; + +import com.google.gson.Gson; +import com.vernu.sms.ApiManager; +import com.vernu.sms.dtos.SMSDTO; +import com.vernu.sms.dtos.SMSForwardResponseDTO; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import retrofit2.Call; +import retrofit2.Response; + +public class SMSReceivedWorker extends Worker { + private static final String TAG = "SMSReceivedWorker"; + private static final int MAX_RETRIES = 5; + + public static final String KEY_DEVICE_ID = "device_id"; + public static final String KEY_API_KEY = "api_key"; + public static final String KEY_SMS_DTO = "sms_dto"; + public static final String KEY_RETRY_COUNT = "retry_count"; + + public SMSReceivedWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + String deviceId = getInputData().getString(KEY_DEVICE_ID); + String apiKey = getInputData().getString(KEY_API_KEY); + String smsDtoJson = getInputData().getString(KEY_SMS_DTO); + int retryCount = getInputData().getInt(KEY_RETRY_COUNT, 0); + + if (deviceId == null || apiKey == null || smsDtoJson == null) { + Log.e(TAG, "Missing required parameters"); + return Result.failure(); + } + + // Check if we've exceeded the maximum retry count + if (retryCount >= MAX_RETRIES) { + Log.e(TAG, "Maximum retry count reached for received SMS"); + return Result.failure(); + } + + SMSDTO smsDTO = new Gson().fromJson(smsDtoJson, SMSDTO.class); + + try { + Call call = ApiManager.getApiService().sendReceivedSMS(deviceId, apiKey, smsDTO); + Response response = call.execute(); + + if (response.isSuccessful()) { + Log.d(TAG, "Received SMS sent to server successfully"); + return Result.success(); + } else { + Log.e(TAG, "Failed to send received SMS to server. Response code: " + response.code()); + return Result.retry(); + } + } catch (IOException e) { + Log.e(TAG, "API call failed: " + e.getMessage()); + return Result.retry(); + } + } + + public static void enqueueWork(Context context, String deviceId, String apiKey, SMSDTO smsDTO) { + Data inputData = new Data.Builder() + .putString(KEY_DEVICE_ID, deviceId) + .putString(KEY_API_KEY, apiKey) + .putString(KEY_SMS_DTO, new Gson().toJson(smsDTO)) + .putInt(KEY_RETRY_COUNT, 0) + .build(); + + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build(); + + OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(SMSReceivedWorker.class) + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS) + .setInputData(inputData) + .addTag("sms_received") + .build(); + + String uniqueWorkName = "sms_received_" + System.currentTimeMillis(); + WorkManager.getInstance(context) + .beginUniqueWork(uniqueWorkName, + androidx.work.ExistingWorkPolicy.APPEND_OR_REPLACE, + workRequest) + .enqueue(); + + Log.d(TAG, "Work enqueued for received SMS from: " + smsDTO.getSender()); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.java b/android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.java new file mode 100644 index 0000000..6549be2 --- /dev/null +++ b/android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.java @@ -0,0 +1,105 @@ +package com.vernu.sms.workers; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.Data; +import androidx.work.Worker; +import androidx.work.WorkerParameters; +import androidx.work.BackoffPolicy; +import androidx.work.Constraints; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; + +import com.google.gson.Gson; +import com.vernu.sms.ApiManager; +import com.vernu.sms.dtos.SMSDTO; +import com.vernu.sms.dtos.SMSForwardResponseDTO; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import retrofit2.Call; +import retrofit2.Response; + +public class SMSStatusUpdateWorker extends Worker { + private static final String TAG = "SMSStatusUpdateWorker"; + private static final int MAX_RETRIES = 5; + + public static final String KEY_DEVICE_ID = "device_id"; + public static final String KEY_API_KEY = "api_key"; + public static final String KEY_SMS_DTO = "sms_dto"; + public static final String KEY_RETRY_COUNT = "retry_count"; + + public SMSStatusUpdateWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + String deviceId = getInputData().getString(KEY_DEVICE_ID); + String apiKey = getInputData().getString(KEY_API_KEY); + String smsDtoJson = getInputData().getString(KEY_SMS_DTO); + int retryCount = getInputData().getInt(KEY_RETRY_COUNT, 0); + + if (deviceId == null || apiKey == null || smsDtoJson == null) { + Log.e(TAG, "Missing required parameters"); + return Result.failure(); + } + + // Check if we've exceeded the maximum retry count + if (retryCount >= MAX_RETRIES) { + Log.e(TAG, "Maximum retry count reached for SMS status update"); + return Result.failure(); + } + + SMSDTO smsDTO = new Gson().fromJson(smsDtoJson, SMSDTO.class); + + try { + Call call = ApiManager.getApiService().updateSMSStatus(deviceId, apiKey, smsDTO); + Response response = call.execute(); + + if (response.isSuccessful()) { + Log.d(TAG, "SMS status updated successfully - ID: " + smsDTO.getSmsId() + ", Status: " + smsDTO.getStatus()); + return Result.success(); + } else { + Log.e(TAG, "Failed to update SMS status. Response code: " + response.code()); + return Result.retry(); + } + } catch (IOException e) { + Log.e(TAG, "API call failed: " + e.getMessage()); + return Result.retry(); + } + } + + public static void enqueueWork(Context context, String deviceId, String apiKey, SMSDTO smsDTO) { + Data inputData = new Data.Builder() + .putString(KEY_DEVICE_ID, deviceId) + .putString(KEY_API_KEY, apiKey) + .putString(KEY_SMS_DTO, new Gson().toJson(smsDTO)) + .putInt(KEY_RETRY_COUNT, 0) + .build(); + + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build(); + + OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(SMSStatusUpdateWorker.class) + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS) + .setInputData(inputData) + .build(); + + String uniqueWorkName = "sms_status_" + smsDTO.getStatus() + "_" + System.currentTimeMillis(); + WorkManager.getInstance(context) + .beginUniqueWork(uniqueWorkName, + androidx.work.ExistingWorkPolicy.REPLACE, + workRequest) + .enqueue(); + + Log.d(TAG, "Work enqueued for SMS status update - ID: " + smsDTO.getSmsId()); + } +} \ No newline at end of file