Browse Source

Merge pull request #96 from vernu/improve-call-reliability

Improve received sms and status tracking reliability using work manager
pull/98/head
Israel Abebe 9 months ago
committed by GitHub
parent
commit
f520f378d0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      android/app/build.gradle
  2. 8
      android/app/src/main/AndroidManifest.xml
  3. 20
      android/app/src/main/java/com/vernu/sms/SMSGatewayApplication.java
  4. 33
      android/app/src/main/java/com/vernu/sms/receivers/SMSBroadcastReceiver.java
  5. 26
      android/app/src/main/java/com/vernu/sms/receivers/SMSStatusReceiver.java
  6. 106
      android/app/src/main/java/com/vernu/sms/workers/SMSReceivedWorker.java
  7. 105
      android/app/src/main/java/com/vernu/sms/workers/SMSStatusUpdateWorker.java

6
android/app/build.gradle

@ -11,8 +11,8 @@ android {
applicationId "com.vernu.sms" applicationId "com.vernu.sms"
minSdk 24 minSdk 24
targetSdk 32 targetSdk 32
versionCode 14
versionName "2.6.1"
versionCode 15
versionName "2.6.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -55,6 +55,8 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.journeyapps:zxing-android-embedded:4.1.0' implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
implementation 'androidx.work:work-runtime:2.7.1'
// def room_version = "2.4.2" // def room_version = "2.4.2"
// implementation "androidx.room:room-runtime:$room_version" // implementation "androidx.room:room-runtime:$room_version"
// annotationProcessor "androidx.room:room-compiler:$room_version" // annotationProcessor "androidx.room:room-compiler:$room_version"

8
android/app/src/main/AndroidManifest.xml

@ -14,7 +14,9 @@
<uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" /> <uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".SMSGatewayApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
@ -22,6 +24,12 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.SMSGateway" android:theme="@style/Theme.SMSGateway"
android:usesCleartextTraffic="true" > android:usesCleartextTraffic="true" >
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<service <service
android:name=".services.FCMService" android:name=".services.FCMService"
android:exported="false" android:exported="false"

20
android/app/src/main/java/com/vernu/sms/SMSGatewayApplication.java

@ -0,0 +1,20 @@
package com.vernu.sms;
import android.app.Application;
import androidx.work.Configuration;
import androidx.work.WorkManager;
public class SMSGatewayApplication extends Application implements Configuration.Provider {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public Configuration getWorkManagerConfiguration() {
return new Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build();
}
}

33
android/app/src/main/java/com/vernu/sms/receivers/SMSBroadcastReceiver.java

@ -6,17 +6,13 @@ import android.content.Intent;
import android.provider.Telephony; import android.provider.Telephony;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import android.util.Log; import android.util.Log;
import com.vernu.sms.ApiManager;
import com.vernu.sms.AppConstants; import com.vernu.sms.AppConstants;
import com.vernu.sms.dtos.SMSDTO; import com.vernu.sms.dtos.SMSDTO;
import com.vernu.sms.dtos.SMSForwardResponseDTO;
import com.vernu.sms.helpers.SharedPreferenceHelper; import com.vernu.sms.helpers.SharedPreferenceHelper;
import com.vernu.sms.workers.SMSReceivedWorker;
import java.util.Date;
import java.util.Objects; import java.util.Objects;
import retrofit2.Call;
import retrofit2.Response;
public class SMSBroadcastReceiver extends BroadcastReceiver { public class SMSBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "SMSBroadcastReceiver"; private static final String TAG = "SMSBroadcastReceiver";
@ -64,32 +60,7 @@ public class SMSBroadcastReceiver extends BroadcastReceiver {
// receivedSMSDTO.setMessage(receivedSMS.getMessage()); // receivedSMSDTO.setMessage(receivedSMS.getMessage());
// receivedSMSDTO.setReceivedAt(receivedSMS.getReceivedAt()); // receivedSMSDTO.setReceivedAt(receivedSMS.getReceivedAt());
Call<SMSForwardResponseDTO> apiCall = ApiManager.getApiService().sendReceivedSMS(deviceId, apiKey, receivedSMSDTO);
apiCall.enqueue(new retrofit2.Callback<SMSForwardResponseDTO>() {
@Override
public void onResponse(Call<SMSForwardResponseDTO> call, Response<SMSForwardResponseDTO> 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<SMSForwardResponseDTO> 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) { // private void updateLocalReceivedSMS(SMS localReceivedSMS, Context context) {

26
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.telephony.SmsManager;
import android.util.Log; import android.util.Log;
import com.vernu.sms.ApiManager;
import com.vernu.sms.AppConstants; import com.vernu.sms.AppConstants;
import com.vernu.sms.dtos.SMSDTO; import com.vernu.sms.dtos.SMSDTO;
import com.vernu.sms.dtos.SMSForwardResponseDTO;
import com.vernu.sms.helpers.SharedPreferenceHelper; 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 { public class SMSStatusReceiver extends BroadcastReceiver {
private static final String TAG = "SMSStatusReceiver"; private static final String TAG = "SMSStatusReceiver";
@ -158,23 +153,6 @@ public class SMSStatusReceiver extends BroadcastReceiver {
return; return;
} }
GatewayApiService apiService = ApiManager.getApiService();
Call<SMSForwardResponseDTO> call = apiService.updateSMSStatus(deviceId, apiKey, smsDTO);
call.enqueue(new Callback<SMSForwardResponseDTO>() {
@Override
public void onResponse(Call<SMSForwardResponseDTO> call, Response<SMSForwardResponseDTO> 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<SMSForwardResponseDTO> call, Throwable t) {
Log.e(TAG, "API call failed: " + t.getMessage());
}
});
SMSStatusUpdateWorker.enqueueWork(context, deviceId, apiKey, smsDTO);
} }
} }

106
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<SMSForwardResponseDTO> call = ApiManager.getApiService().sendReceivedSMS(deviceId, apiKey, smsDTO);
Response<SMSForwardResponseDTO> 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());
}
}

105
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<SMSForwardResponseDTO> call = ApiManager.getApiService().updateSMSStatus(deviceId, apiKey, smsDTO);
Response<SMSForwardResponseDTO> 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());
}
}
Loading…
Cancel
Save