mirror of
https://github.com/vernu/textbee.git
synced 2026-02-19 23:26:14 -05:00
feat(android): implement heartbeat check
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<application
|
||||
android:name=".SMSGatewayApplication"
|
||||
android:allowBackup="true"
|
||||
|
||||
@@ -19,4 +19,7 @@ public class AppConstants {
|
||||
public static final String SHARED_PREFS_LAST_VERSION_CODE_KEY = "LAST_VERSION_CODE";
|
||||
public static final String SHARED_PREFS_LAST_VERSION_NAME_KEY = "LAST_VERSION_NAME";
|
||||
public static final String SHARED_PREFS_STICKY_NOTIFICATION_ENABLED_KEY = "STICKY_NOTIFICATION_ENABLED";
|
||||
public static final String HEARTBEAT_WORK_TAG = "heartbeat";
|
||||
public static final String SHARED_PREFS_HEARTBEAT_ENABLED_KEY = "HEARTBEAT_ENABLED";
|
||||
public static final String SHARED_PREFS_HEARTBEAT_INTERVAL_MINUTES_KEY = "HEARTBEAT_INTERVAL_MINUTES";
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.vernu.sms.dtos.RegisterDeviceInputDTO;
|
||||
import com.vernu.sms.dtos.RegisterDeviceResponseDTO;
|
||||
import com.vernu.sms.helpers.SharedPreferenceHelper;
|
||||
import com.vernu.sms.helpers.VersionTracker;
|
||||
import com.vernu.sms.helpers.HeartbeatManager;
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
@@ -107,6 +108,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
Log.d(TAG, "Starting sticky notification service on app start");
|
||||
}
|
||||
|
||||
// Schedule heartbeat if device is enabled and registered
|
||||
if (gatewayEnabled && !deviceId.isEmpty()) {
|
||||
HeartbeatManager.scheduleHeartbeat(mContext);
|
||||
Log.d(TAG, "Scheduling heartbeat on app start");
|
||||
}
|
||||
|
||||
if (deviceId == null || deviceId.isEmpty()) {
|
||||
registerDeviceBtn.setText("Register");
|
||||
} else {
|
||||
@@ -164,8 +171,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (SharedPreferenceHelper.getSharedPreferenceBoolean(mContext, AppConstants.SHARED_PREFS_STICKY_NOTIFICATION_ENABLED_KEY, false)) {
|
||||
TextBeeUtils.startStickyNotificationService(mContext);
|
||||
}
|
||||
// Schedule heartbeat
|
||||
HeartbeatManager.scheduleHeartbeat(mContext);
|
||||
} else {
|
||||
TextBeeUtils.stopStickyNotificationService(mContext);
|
||||
// Cancel heartbeat
|
||||
HeartbeatManager.cancelHeartbeat(mContext);
|
||||
}
|
||||
compoundButton.setEnabled(true);
|
||||
}
|
||||
@@ -389,6 +400,21 @@ public class MainActivity extends AppCompatActivity {
|
||||
SharedPreferenceHelper.setSharedPreferenceString(mContext, AppConstants.SHARED_PREFS_DEVICE_ID_KEY, deviceId);
|
||||
SharedPreferenceHelper.setSharedPreferenceBoolean(mContext, AppConstants.SHARED_PREFS_GATEWAY_ENABLED_KEY, registerDeviceInput.isEnabled());
|
||||
gatewaySwitch.setChecked(registerDeviceInput.isEnabled());
|
||||
|
||||
// Sync heartbeatIntervalMinutes from server response
|
||||
if (response.body().data.get("heartbeatIntervalMinutes") != null) {
|
||||
Object intervalObj = response.body().data.get("heartbeatIntervalMinutes");
|
||||
if (intervalObj instanceof Number) {
|
||||
int intervalMinutes = ((Number) intervalObj).intValue();
|
||||
SharedPreferenceHelper.setSharedPreferenceInt(mContext, AppConstants.SHARED_PREFS_HEARTBEAT_INTERVAL_MINUTES_KEY, intervalMinutes);
|
||||
Log.d(TAG, "Synced heartbeat interval from server: " + intervalMinutes + " minutes");
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule heartbeat if device is enabled
|
||||
if (registerDeviceInput.isEnabled()) {
|
||||
HeartbeatManager.scheduleHeartbeat(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Update stored version information
|
||||
@@ -432,6 +458,21 @@ public class MainActivity extends AppCompatActivity {
|
||||
SharedPreferenceHelper.setSharedPreferenceString(mContext, AppConstants.SHARED_PREFS_DEVICE_ID_KEY, deviceId);
|
||||
SharedPreferenceHelper.setSharedPreferenceBoolean(mContext, AppConstants.SHARED_PREFS_GATEWAY_ENABLED_KEY, registerDeviceInput.isEnabled());
|
||||
gatewaySwitch.setChecked(registerDeviceInput.isEnabled());
|
||||
|
||||
// Sync heartbeatIntervalMinutes from server response
|
||||
if (response.body().data.get("heartbeatIntervalMinutes") != null) {
|
||||
Object intervalObj = response.body().data.get("heartbeatIntervalMinutes");
|
||||
if (intervalObj instanceof Number) {
|
||||
int intervalMinutes = ((Number) intervalObj).intValue();
|
||||
SharedPreferenceHelper.setSharedPreferenceInt(mContext, AppConstants.SHARED_PREFS_HEARTBEAT_INTERVAL_MINUTES_KEY, intervalMinutes);
|
||||
Log.d(TAG, "Synced heartbeat interval from server: " + intervalMinutes + " minutes");
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule heartbeat if device is enabled
|
||||
if (registerDeviceInput.isEnabled()) {
|
||||
HeartbeatManager.scheduleHeartbeat(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Update stored version information
|
||||
@@ -503,6 +544,21 @@ public class MainActivity extends AppCompatActivity {
|
||||
SharedPreferenceHelper.setSharedPreferenceString(mContext, AppConstants.SHARED_PREFS_DEVICE_ID_KEY, deviceId);
|
||||
deviceIdTxt.setText(deviceId);
|
||||
deviceIdEditText.setText(deviceId);
|
||||
|
||||
// Sync heartbeatIntervalMinutes from server response
|
||||
if (response.body().data.get("heartbeatIntervalMinutes") != null) {
|
||||
Object intervalObj = response.body().data.get("heartbeatIntervalMinutes");
|
||||
if (intervalObj instanceof Number) {
|
||||
int intervalMinutes = ((Number) intervalObj).intValue();
|
||||
SharedPreferenceHelper.setSharedPreferenceInt(mContext, AppConstants.SHARED_PREFS_HEARTBEAT_INTERVAL_MINUTES_KEY, intervalMinutes);
|
||||
Log.d(TAG, "Synced heartbeat interval from server: " + intervalMinutes + " minutes");
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule heartbeat if device is enabled
|
||||
if (updateDeviceInput.isEnabled()) {
|
||||
HeartbeatManager.scheduleHeartbeat(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Update stored version information
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.vernu.sms.dtos;
|
||||
|
||||
public class HeartbeatInputDTO {
|
||||
private String fcmToken;
|
||||
private Integer batteryPercentage;
|
||||
private Boolean isCharging;
|
||||
private String networkType;
|
||||
private String appVersionName;
|
||||
private Integer appVersionCode;
|
||||
private Long deviceUptimeMillis;
|
||||
private Long memoryFreeBytes;
|
||||
private Long memoryTotalBytes;
|
||||
private Long memoryMaxBytes;
|
||||
private Long storageAvailableBytes;
|
||||
private Long storageTotalBytes;
|
||||
private String timezone;
|
||||
private String locale;
|
||||
private Boolean receiveSMSEnabled;
|
||||
|
||||
public HeartbeatInputDTO() {
|
||||
}
|
||||
|
||||
public String getFcmToken() {
|
||||
return fcmToken;
|
||||
}
|
||||
|
||||
public void setFcmToken(String fcmToken) {
|
||||
this.fcmToken = fcmToken;
|
||||
}
|
||||
|
||||
public Integer getBatteryPercentage() {
|
||||
return batteryPercentage;
|
||||
}
|
||||
|
||||
public void setBatteryPercentage(Integer batteryPercentage) {
|
||||
this.batteryPercentage = batteryPercentage;
|
||||
}
|
||||
|
||||
public Boolean getIsCharging() {
|
||||
return isCharging;
|
||||
}
|
||||
|
||||
public void setIsCharging(Boolean isCharging) {
|
||||
this.isCharging = isCharging;
|
||||
}
|
||||
|
||||
public String getNetworkType() {
|
||||
return networkType;
|
||||
}
|
||||
|
||||
public void setNetworkType(String networkType) {
|
||||
this.networkType = networkType;
|
||||
}
|
||||
|
||||
public String getAppVersionName() {
|
||||
return appVersionName;
|
||||
}
|
||||
|
||||
public void setAppVersionName(String appVersionName) {
|
||||
this.appVersionName = appVersionName;
|
||||
}
|
||||
|
||||
public Integer getAppVersionCode() {
|
||||
return appVersionCode;
|
||||
}
|
||||
|
||||
public void setAppVersionCode(Integer appVersionCode) {
|
||||
this.appVersionCode = appVersionCode;
|
||||
}
|
||||
|
||||
public Long getDeviceUptimeMillis() {
|
||||
return deviceUptimeMillis;
|
||||
}
|
||||
|
||||
public void setDeviceUptimeMillis(Long deviceUptimeMillis) {
|
||||
this.deviceUptimeMillis = deviceUptimeMillis;
|
||||
}
|
||||
|
||||
public Long getMemoryFreeBytes() {
|
||||
return memoryFreeBytes;
|
||||
}
|
||||
|
||||
public void setMemoryFreeBytes(Long memoryFreeBytes) {
|
||||
this.memoryFreeBytes = memoryFreeBytes;
|
||||
}
|
||||
|
||||
public Long getMemoryTotalBytes() {
|
||||
return memoryTotalBytes;
|
||||
}
|
||||
|
||||
public void setMemoryTotalBytes(Long memoryTotalBytes) {
|
||||
this.memoryTotalBytes = memoryTotalBytes;
|
||||
}
|
||||
|
||||
public Long getMemoryMaxBytes() {
|
||||
return memoryMaxBytes;
|
||||
}
|
||||
|
||||
public void setMemoryMaxBytes(Long memoryMaxBytes) {
|
||||
this.memoryMaxBytes = memoryMaxBytes;
|
||||
}
|
||||
|
||||
public Long getStorageAvailableBytes() {
|
||||
return storageAvailableBytes;
|
||||
}
|
||||
|
||||
public void setStorageAvailableBytes(Long storageAvailableBytes) {
|
||||
this.storageAvailableBytes = storageAvailableBytes;
|
||||
}
|
||||
|
||||
public Long getStorageTotalBytes() {
|
||||
return storageTotalBytes;
|
||||
}
|
||||
|
||||
public void setStorageTotalBytes(Long storageTotalBytes) {
|
||||
this.storageTotalBytes = storageTotalBytes;
|
||||
}
|
||||
|
||||
public String getTimezone() {
|
||||
return timezone;
|
||||
}
|
||||
|
||||
public void setTimezone(String timezone) {
|
||||
this.timezone = timezone;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public void setLocale(String locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public Boolean getReceiveSMSEnabled() {
|
||||
return receiveSMSEnabled;
|
||||
}
|
||||
|
||||
public void setReceiveSMSEnabled(Boolean receiveSMSEnabled) {
|
||||
this.receiveSMSEnabled = receiveSMSEnabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.vernu.sms.dtos;
|
||||
|
||||
public class HeartbeatResponseDTO {
|
||||
public boolean success;
|
||||
public boolean fcmTokenUpdated;
|
||||
public long lastHeartbeat;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.vernu.sms.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.work.Constraints;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.PeriodicWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import com.vernu.sms.AppConstants;
|
||||
import com.vernu.sms.workers.HeartbeatWorker;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class HeartbeatManager {
|
||||
private static final String TAG = "HeartbeatManager";
|
||||
private static final int MIN_INTERVAL_MINUTES = 15; // Android WorkManager minimum
|
||||
|
||||
public static void scheduleHeartbeat(Context context) {
|
||||
// Get interval from shared preferences (default 30 minutes)
|
||||
int intervalMinutes = SharedPreferenceHelper.getSharedPreferenceInt(
|
||||
context,
|
||||
AppConstants.SHARED_PREFS_HEARTBEAT_INTERVAL_MINUTES_KEY,
|
||||
30
|
||||
);
|
||||
|
||||
// Enforce minimum interval
|
||||
if (intervalMinutes < MIN_INTERVAL_MINUTES) {
|
||||
Log.w(TAG, "Interval " + intervalMinutes + " minutes is less than minimum " + MIN_INTERVAL_MINUTES + " minutes, using minimum");
|
||||
intervalMinutes = MIN_INTERVAL_MINUTES;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Scheduling heartbeat with interval: " + intervalMinutes + " minutes");
|
||||
|
||||
// Cancel any existing heartbeat work
|
||||
cancelHeartbeat(context);
|
||||
|
||||
// Create constraints
|
||||
Constraints constraints = new Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build();
|
||||
|
||||
// Create periodic work request
|
||||
PeriodicWorkRequest heartbeatWork = new PeriodicWorkRequest.Builder(
|
||||
HeartbeatWorker.class,
|
||||
intervalMinutes,
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.setConstraints(constraints)
|
||||
.addTag(AppConstants.HEARTBEAT_WORK_TAG)
|
||||
.build();
|
||||
|
||||
// Enqueue the work
|
||||
WorkManager.getInstance(context)
|
||||
.enqueue(heartbeatWork);
|
||||
|
||||
Log.d(TAG, "Heartbeat scheduled successfully");
|
||||
}
|
||||
|
||||
public static void cancelHeartbeat(Context context) {
|
||||
Log.d(TAG, "Cancelling heartbeat work");
|
||||
WorkManager.getInstance(context)
|
||||
.cancelAllWorkByTag(AppConstants.HEARTBEAT_WORK_TAG);
|
||||
}
|
||||
|
||||
public static void triggerHeartbeat(Context context) {
|
||||
// This can be used for testing - trigger immediate heartbeat
|
||||
Log.d(TAG, "Triggering immediate heartbeat");
|
||||
// For immediate execution, we could create a OneTimeWorkRequest
|
||||
// but for now, just reschedule which will run soon
|
||||
scheduleHeartbeat(context);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import com.vernu.sms.TextBeeUtils;
|
||||
import com.vernu.sms.dtos.RegisterDeviceInputDTO;
|
||||
import com.vernu.sms.dtos.RegisterDeviceResponseDTO;
|
||||
import com.vernu.sms.helpers.SharedPreferenceHelper;
|
||||
import com.vernu.sms.helpers.HeartbeatManager;
|
||||
import com.vernu.sms.services.StickyNotificationService;
|
||||
|
||||
import retrofit2.Call;
|
||||
@@ -54,6 +55,17 @@ public class BootCompletedReceiver extends BroadcastReceiver {
|
||||
// Only proceed if both device ID and API key are available
|
||||
if (!deviceId.isEmpty() && !apiKey.isEmpty()) {
|
||||
updateDeviceInfo(context, deviceId, apiKey);
|
||||
|
||||
// Schedule heartbeat if device is enabled
|
||||
boolean deviceEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean(
|
||||
context,
|
||||
AppConstants.SHARED_PREFS_GATEWAY_ENABLED_KEY,
|
||||
false
|
||||
);
|
||||
if (deviceEnabled) {
|
||||
Log.i(TAG, "Device booted, scheduling heartbeat");
|
||||
HeartbeatManager.scheduleHeartbeat(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +97,18 @@ public class BootCompletedReceiver extends BroadcastReceiver {
|
||||
public void onResponse(Call<RegisterDeviceResponseDTO> call, Response<RegisterDeviceResponseDTO> response) {
|
||||
if (response.isSuccessful()) {
|
||||
Log.d(TAG, "Device info updated successfully after boot");
|
||||
|
||||
// Sync heartbeatIntervalMinutes from server response
|
||||
if (response.body() != null && response.body().data != null) {
|
||||
if (response.body().data.get("heartbeatIntervalMinutes") != null) {
|
||||
Object intervalObj = response.body().data.get("heartbeatIntervalMinutes");
|
||||
if (intervalObj instanceof Number) {
|
||||
int intervalMinutes = ((Number) intervalObj).intValue();
|
||||
SharedPreferenceHelper.setSharedPreferenceInt(context, AppConstants.SHARED_PREFS_HEARTBEAT_INTERVAL_MINUTES_KEY, intervalMinutes);
|
||||
Log.d(TAG, "Synced heartbeat interval from server: " + intervalMinutes + " minutes");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Failed to update device info after boot. Response code: " + response.code());
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.vernu.sms.dtos.SMSDTO;
|
||||
import com.vernu.sms.dtos.SMSForwardResponseDTO;
|
||||
import com.vernu.sms.dtos.RegisterDeviceInputDTO;
|
||||
import com.vernu.sms.dtos.RegisterDeviceResponseDTO;
|
||||
import com.vernu.sms.dtos.HeartbeatInputDTO;
|
||||
import com.vernu.sms.dtos.HeartbeatResponseDTO;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
@@ -24,4 +26,7 @@ public interface GatewayApiService {
|
||||
|
||||
@PATCH("gateway/devices/{deviceId}/sms-status")
|
||||
Call<SMSForwardResponseDTO> updateSMSStatus(@Path("deviceId") String deviceId, @Header("x-api-key") String apiKey, @Body() SMSDTO body);
|
||||
|
||||
@POST("gateway/devices/{deviceId}/heartbeat")
|
||||
Call<HeartbeatResponseDTO> heartbeat(@Path("deviceId") String deviceId, @Header("x-api-key") String apiKey, @Body() HeartbeatInputDTO body);
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.vernu.sms.workers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
import android.os.StatFs;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging;
|
||||
import com.vernu.sms.ApiManager;
|
||||
import com.vernu.sms.AppConstants;
|
||||
import com.vernu.sms.BuildConfig;
|
||||
import com.vernu.sms.dtos.HeartbeatInputDTO;
|
||||
import com.vernu.sms.dtos.HeartbeatResponseDTO;
|
||||
import com.vernu.sms.helpers.SharedPreferenceHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class HeartbeatWorker extends Worker {
|
||||
private static final String TAG = "HeartbeatWorker";
|
||||
|
||||
public HeartbeatWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
Context context = getApplicationContext();
|
||||
|
||||
// Check if device is registered
|
||||
String deviceId = SharedPreferenceHelper.getSharedPreferenceString(
|
||||
context,
|
||||
AppConstants.SHARED_PREFS_DEVICE_ID_KEY,
|
||||
""
|
||||
);
|
||||
|
||||
if (deviceId.isEmpty()) {
|
||||
Log.d(TAG, "Device not registered, skipping heartbeat");
|
||||
return Result.success(); // Not a failure, just skip
|
||||
}
|
||||
|
||||
// Check if device is enabled
|
||||
boolean deviceEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean(
|
||||
context,
|
||||
AppConstants.SHARED_PREFS_GATEWAY_ENABLED_KEY,
|
||||
false
|
||||
);
|
||||
|
||||
if (!deviceEnabled) {
|
||||
Log.d(TAG, "Device not enabled, skipping heartbeat");
|
||||
return Result.success(); // Not a failure, just skip
|
||||
}
|
||||
|
||||
// Check if heartbeat feature is enabled
|
||||
boolean heartbeatEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean(
|
||||
context,
|
||||
AppConstants.SHARED_PREFS_HEARTBEAT_ENABLED_KEY,
|
||||
true // Default to true
|
||||
);
|
||||
|
||||
if (!heartbeatEnabled) {
|
||||
Log.d(TAG, "Heartbeat feature disabled, skipping heartbeat");
|
||||
return Result.success(); // Not a failure, just skip
|
||||
}
|
||||
|
||||
String apiKey = SharedPreferenceHelper.getSharedPreferenceString(
|
||||
context,
|
||||
AppConstants.SHARED_PREFS_API_KEY_KEY,
|
||||
""
|
||||
);
|
||||
|
||||
if (apiKey.isEmpty()) {
|
||||
Log.e(TAG, "API key not available, skipping heartbeat");
|
||||
return Result.success(); // Not a failure, just skip
|
||||
}
|
||||
|
||||
// Collect device information
|
||||
HeartbeatInputDTO heartbeatInput = new HeartbeatInputDTO();
|
||||
|
||||
try {
|
||||
// Get FCM token (blocking wait)
|
||||
try {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
final String[] fcmToken = new String[1];
|
||||
FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful()) {
|
||||
fcmToken[0] = task.getResult();
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
if (latch.await(5, TimeUnit.SECONDS) && fcmToken[0] != null) {
|
||||
heartbeatInput.setFcmToken(fcmToken[0]);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to get FCM token: " + e.getMessage());
|
||||
// Continue without FCM token
|
||||
}
|
||||
|
||||
// Get battery information
|
||||
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
Intent batteryStatus = context.registerReceiver(null, ifilter);
|
||||
if (batteryStatus != null) {
|
||||
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
||||
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
|
||||
int batteryPct = (int) ((level / (float) scale) * 100);
|
||||
heartbeatInput.setBatteryPercentage(batteryPct);
|
||||
|
||||
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
|
||||
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||
status == BatteryManager.BATTERY_STATUS_FULL;
|
||||
heartbeatInput.setIsCharging(isCharging);
|
||||
}
|
||||
|
||||
// Get network type
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cm != null) {
|
||||
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
||||
if (activeNetwork != null && activeNetwork.isConnected()) {
|
||||
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
heartbeatInput.setNetworkType("wifi");
|
||||
} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
|
||||
heartbeatInput.setNetworkType("cellular");
|
||||
} else {
|
||||
heartbeatInput.setNetworkType("none");
|
||||
}
|
||||
} else {
|
||||
heartbeatInput.setNetworkType("none");
|
||||
}
|
||||
}
|
||||
|
||||
// Get app version
|
||||
heartbeatInput.setAppVersionName(BuildConfig.VERSION_NAME);
|
||||
heartbeatInput.setAppVersionCode(BuildConfig.VERSION_CODE);
|
||||
|
||||
// Get device uptime
|
||||
heartbeatInput.setDeviceUptimeMillis(SystemClock.uptimeMillis());
|
||||
|
||||
// Get memory information
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
heartbeatInput.setMemoryFreeBytes(runtime.freeMemory());
|
||||
heartbeatInput.setMemoryTotalBytes(runtime.totalMemory());
|
||||
heartbeatInput.setMemoryMaxBytes(runtime.maxMemory());
|
||||
|
||||
// Get storage information
|
||||
File internalStorage = context.getFilesDir();
|
||||
StatFs statFs = new StatFs(internalStorage.getPath());
|
||||
long availableBytes = statFs.getAvailableBytes();
|
||||
long totalBytes = statFs.getTotalBytes();
|
||||
heartbeatInput.setStorageAvailableBytes(availableBytes);
|
||||
heartbeatInput.setStorageTotalBytes(totalBytes);
|
||||
|
||||
// Get system information
|
||||
heartbeatInput.setTimezone(TimeZone.getDefault().getID());
|
||||
heartbeatInput.setLocale(Locale.getDefault().toString());
|
||||
|
||||
// Get receive SMS enabled status
|
||||
boolean receiveSMSEnabled = SharedPreferenceHelper.getSharedPreferenceBoolean(
|
||||
context,
|
||||
AppConstants.SHARED_PREFS_RECEIVE_SMS_ENABLED_KEY,
|
||||
false
|
||||
);
|
||||
heartbeatInput.setReceiveSMSEnabled(receiveSMSEnabled);
|
||||
|
||||
// Send heartbeat request
|
||||
Call<HeartbeatResponseDTO> call = ApiManager.getApiService().heartbeat(deviceId, apiKey, heartbeatInput);
|
||||
Response<HeartbeatResponseDTO> response = call.execute();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
HeartbeatResponseDTO responseBody = response.body();
|
||||
if (responseBody.fcmTokenUpdated) {
|
||||
Log.d(TAG, "FCM token was updated during heartbeat");
|
||||
}
|
||||
Log.d(TAG, "Heartbeat sent successfully");
|
||||
return Result.success();
|
||||
} else {
|
||||
Log.e(TAG, "Failed to send heartbeat. Response code: " + (response.code()));
|
||||
return Result.retry();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Heartbeat API call failed: " + e.getMessage());
|
||||
return Result.retry();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error collecting device information: " + e.getMessage());
|
||||
return Result.retry();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user