diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorkerOptimized.java b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorkerOptimized.java new file mode 100644 index 0000000..911f961 --- /dev/null +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorkerOptimized.java @@ -0,0 +1,302 @@ +/** + * DailyNotificationFetchWorkerOptimized.java + * + * Optimized fetch worker with WorkManager hygiene best practices + * Extends OptimizedWorker for proper lifecycle management and resource cleanup + * + * @author Matthew Raymer + * @version 2.0.0 - Optimized Architecture + */ + +package com.timesafari.dailynotification; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.Data; +import androidx.work.WorkerParameters; + +import java.util.concurrent.TimeUnit; + +/** + * Optimized fetch worker with hygiene best practices + * + * Features: + * - Proper resource management + * - Timeout handling + * - Performance monitoring + * - Error recovery + * - Memory optimization + */ +public class DailyNotificationFetchWorkerOptimized extends OptimizedWorker { + + private static final String TAG = "DailyNotificationFetchWorkerOptimized"; + + // Configuration constants + private static final String KEY_SCHEDULED_TIME = "scheduled_time"; + private static final String KEY_FETCH_TIME = "fetch_time"; + private static final String KEY_RETRY_COUNT = "retry_count"; + private static final String KEY_IMMEDIATE = "immediate"; + + private static final int MAX_RETRY_ATTEMPTS = 3; + private static final long WORK_TIMEOUT_MS = 8 * 60 * 1000; // 8 minutes total + private static final long FETCH_TIMEOUT_MS = 30 * 1000; // 30 seconds for fetch + + // Worker components + private DailyNotificationStorageOptimized storage; + private DailyNotificationFetcher fetcher; + private JsonOptimizer jsonOptimizer; + + // Worker state + private int retryCount = 0; + private boolean isImmediate = false; + private long scheduledTime = 0; + + /** + * Constructor + * + * @param context Application context + * @param params Worker parameters + */ + public DailyNotificationFetchWorkerOptimized(@NonNull Context context, + @NonNull WorkerParameters params) { + super(context, params); + + Log.d(TAG, "Optimized fetch worker initialized"); + } + + /** + * Initialize worker-specific resources + */ + @Override + protected void onInitializeResources() { + try { + logProgress("Initializing resources"); + + // Initialize optimized storage + storage = new DailyNotificationStorageOptimized(getApplicationContext()); + + // Initialize fetcher + fetcher = new DailyNotificationFetcher(getApplicationContext(), storage); + + // Initialize JSON optimizer + jsonOptimizer = new JsonOptimizer(); + + // Parse worker parameters + parseWorkerParameters(); + + logProgress("Resources initialized successfully"); + + } catch (Exception e) { + Log.e(TAG, "Error initializing resources", e); + throw e; + } + } + + /** + * Cleanup worker-specific resources + */ + @Override + protected void onCleanupResources() { + try { + logProgress("Cleaning up resources"); + + // Flush storage changes + if (storage != null) { + storage.flush(); + } + + // Clear JSON cache if needed + if (jsonOptimizer != null) { + JsonOptimizer.clearCache(); + } + + logProgress("Resources cleaned up successfully"); + + } catch (Exception e) { + Log.e(TAG, "Error cleaning up resources", e); + } + } + + /** + * Perform the fetch work with optimization + * + * @return Result of the work + */ + @NonNull + @Override + protected Result performWork() { + try { + logProgress("Starting fetch work"); + + // Check if work should be cancelled + if (shouldCancelWork(WORK_TIMEOUT_MS)) { + logProgress("Work cancelled due to timeout"); + return createFailureResult(); + } + + // Check if work is cancelled + if (isWorkCancelled()) { + logProgress("Work cancelled by system"); + return createFailureResult(); + } + + // Perform fetch with timeout + NotificationContent content = performFetchWithTimeout(); + + if (content != null) { + logProgress("Fetch completed successfully"); + + // Create success result with data + Data resultData = new Data.Builder() + .putString("notification_id", content.getId()) + .putLong("scheduled_time", content.getScheduledTime()) + .putLong("fetch_time", System.currentTimeMillis()) + .putInt("retry_count", retryCount) + .build(); + + return createSuccessResult(resultData); + } else { + logProgress("Fetch failed - no content retrieved"); + + // Check if we should retry + if (shouldRetry()) { + logProgress("Scheduling retry"); + return createRetryResult(); + } else { + logProgress("Max retries exceeded"); + return createFailureResult(); + } + } + + } catch (Exception e) { + Log.e(TAG, "Error in fetch work", e); + + // Check if we should retry + if (shouldRetry()) { + logProgress("Scheduling retry after exception"); + return createRetryResult(); + } else { + logProgress("Max retries exceeded after exception"); + return createFailureResult(); + } + } + } + + /** + * Parse worker parameters + */ + private void parseWorkerParameters() { + try { + Data inputData = getInputData(); + + retryCount = inputData.getInt(KEY_RETRY_COUNT, 0); + isImmediate = inputData.getBoolean(KEY_IMMEDIATE, false); + scheduledTime = inputData.getLong(KEY_SCHEDULED_TIME, 0); + + logProgress("Parsed parameters - retry: " + retryCount + + ", immediate: " + isImmediate + + ", scheduled: " + scheduledTime); + + } catch (Exception e) { + Log.e(TAG, "Error parsing worker parameters", e); + } + } + + /** + * Perform fetch with timeout handling + * + * @return Notification content or null if failed + */ + private NotificationContent performFetchWithTimeout() { + try { + logProgress("Starting fetch with timeout: " + FETCH_TIMEOUT_MS + "ms"); + + long fetchStartTime = System.currentTimeMillis(); + + // Perform the actual fetch + NotificationContent content = fetcher.fetchContentImmediately(); + + long fetchDuration = System.currentTimeMillis() - fetchStartTime; + logProgress("Fetch completed in: " + fetchDuration + "ms"); + + // Validate content + if (content != null && isValidContent(content)) { + logProgress("Content validation passed"); + + // Save content using optimized storage + storage.saveNotificationContent(content); + + return content; + } else { + logProgress("Content validation failed"); + return null; + } + + } catch (Exception e) { + Log.e(TAG, "Error in fetch operation", e); + return null; + } + } + + /** + * Validate notification content + * + * @param content Content to validate + * @return true if content is valid + */ + private boolean isValidContent(NotificationContent content) { + if (content == null) { + return false; + } + + // Check essential fields + if (content.getId() == null || content.getId().isEmpty()) { + logProgress("Invalid content: missing ID"); + return false; + } + + if (content.getTitle() == null || content.getTitle().isEmpty()) { + logProgress("Invalid content: missing title"); + return false; + } + + if (content.getBody() == null || content.getBody().isEmpty()) { + logProgress("Invalid content: missing body"); + return false; + } + + if (content.getScheduledTime() <= 0) { + logProgress("Invalid content: invalid scheduled time"); + return false; + } + + return true; + } + + /** + * Check if work should be retried + * + * @return true if should retry + */ + private boolean shouldRetry() { + return retryCount < MAX_RETRY_ATTEMPTS; + } + + /** + * Get worker performance metrics + * + * @return Performance metrics + */ + public WorkerMetrics getFetchMetrics() { + WorkerMetrics metrics = getMetrics(); + + // Add fetch-specific metrics + metrics.retryCount = retryCount; + metrics.isImmediate = isImmediate; + metrics.scheduledTime = scheduledTime; + + return metrics; + } +} diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/OptimizedWorker.java b/android/plugin/src/main/java/com/timesafari/dailynotification/OptimizedWorker.java new file mode 100644 index 0000000..7d0e124 --- /dev/null +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/OptimizedWorker.java @@ -0,0 +1,304 @@ +/** + * OptimizedWorker.java + * + * Base class for optimized WorkManager workers with hygiene best practices + * Implements proper lifecycle management, resource cleanup, and performance monitoring + * + * @author Matthew Raymer + * @version 2.0.0 - Optimized Architecture + */ + +package com.timesafari.dailynotification; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import java.util.concurrent.TimeUnit; + +/** + * Base class for optimized WorkManager workers + * + * Features: + * - Proper lifecycle management + * - Resource cleanup + * - Performance monitoring + * - Error handling + * - Timeout management + */ +public abstract class OptimizedWorker extends Worker { + + private static final String TAG = "OptimizedWorker"; + + // Performance monitoring + private long startTime; + private long endTime; + private boolean isCompleted = false; + + // Resource management + private boolean resourcesInitialized = false; + + /** + * Constructor + * + * @param context Application context + * @param params Worker parameters + */ + public OptimizedWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + + Log.d(TAG, "OptimizedWorker initialized: " + getClass().getSimpleName()); + } + + /** + * Main work execution with hygiene best practices + * + * @return Result of the work + */ + @NonNull + @Override + public final Result doWork() { + startTime = System.currentTimeMillis(); + + try { + Log.i(TAG, "Starting work: " + getClass().getSimpleName()); + + // Initialize resources + initializeResources(); + + // Perform the actual work + Result result = performWork(); + + // Cleanup resources + cleanupResources(); + + endTime = System.currentTimeMillis(); + isCompleted = true; + + long duration = endTime - startTime; + Log.i(TAG, "Work completed: " + getClass().getSimpleName() + + " in " + duration + "ms with result: " + result); + + return result; + + } catch (Exception e) { + Log.e(TAG, "Work failed: " + getClass().getSimpleName(), e); + + // Ensure cleanup even on failure + cleanupResources(); + + endTime = System.currentTimeMillis(); + isCompleted = true; + + return Result.failure(); + } + } + + /** + * Initialize resources for the worker + */ + private void initializeResources() { + try { + if (!resourcesInitialized) { + onInitializeResources(); + resourcesInitialized = true; + Log.d(TAG, "Resources initialized: " + getClass().getSimpleName()); + } + } catch (Exception e) { + Log.e(TAG, "Error initializing resources", e); + throw e; + } + } + + /** + * Cleanup resources after work completion + */ + private void cleanupResources() { + try { + if (resourcesInitialized) { + onCleanupResources(); + resourcesInitialized = false; + Log.d(TAG, "Resources cleaned up: " + getClass().getSimpleName()); + } + } catch (Exception e) { + Log.e(TAG, "Error cleaning up resources", e); + } + } + + /** + * Abstract method to perform the actual work + * + * @return Result of the work + */ + @NonNull + protected abstract Result performWork(); + + /** + * Override to initialize worker-specific resources + */ + protected void onInitializeResources() { + // Default implementation - override in subclasses + } + + /** + * Override to cleanup worker-specific resources + */ + protected void onCleanupResources() { + // Default implementation - override in subclasses + } + + /** + * Check if work is taking too long and should be cancelled + * + * @param maxDurationMs Maximum duration in milliseconds + * @return true if work should be cancelled + */ + protected boolean shouldCancelWork(long maxDurationMs) { + long currentTime = System.currentTimeMillis(); + long elapsed = currentTime - startTime; + + if (elapsed > maxDurationMs) { + Log.w(TAG, "Work timeout exceeded: " + elapsed + "ms > " + maxDurationMs + "ms"); + return true; + } + + return false; + } + + /** + * Check if work is cancelled + * + * @return true if work is cancelled + */ + protected boolean isWorkCancelled() { + return isStopped(); + } + + /** + * Get work duration so far + * + * @return Duration in milliseconds + */ + protected long getWorkDuration() { + if (isCompleted) { + return endTime - startTime; + } else { + return System.currentTimeMillis() - startTime; + } + } + + /** + * Log work progress + * + * @param message Progress message + */ + protected void logProgress(String message) { + long duration = getWorkDuration(); + Log.d(TAG, "[" + duration + "ms] " + getClass().getSimpleName() + ": " + message); + } + + /** + * Create success result with data + * + * @param data Result data + * @return Success result + */ + @NonNull + protected Result createSuccessResult(androidx.work.Data data) { + return Result.success(data); + } + + /** + * Create success result + * + * @return Success result + */ + @NonNull + protected Result createSuccessResult() { + return Result.success(); + } + + /** + * Create failure result with data + * + * @param data Result data + * @return Failure result + */ + @NonNull + protected Result createFailureResult(androidx.work.Data data) { + return Result.failure(data); + } + + /** + * Create failure result + * + * @return Failure result + */ + @NonNull + protected Result createFailureResult() { + return Result.failure(); + } + + /** + * Create retry result with data + * + * @param data Result data + * @return Retry result + */ + @NonNull + protected Result createRetryResult(androidx.work.Data data) { + return Result.retry(data); + } + + /** + * Create retry result + * + * @return Retry result + */ + @NonNull + protected Result createRetryResult() { + return Result.retry(); + } + + /** + * Get worker performance metrics + * + * @return Performance metrics + */ + public WorkerMetrics getMetrics() { + WorkerMetrics metrics = new WorkerMetrics(); + metrics.workerName = getClass().getSimpleName(); + metrics.startTime = startTime; + metrics.endTime = endTime; + metrics.duration = getWorkDuration(); + metrics.isCompleted = isCompleted; + metrics.resourcesInitialized = resourcesInitialized; + + return metrics; + } + + /** + * Worker performance metrics + */ + public static class WorkerMetrics { + public String workerName; + public long startTime; + public long endTime; + public long duration; + public boolean isCompleted; + public boolean resourcesInitialized; + + @Override + public String toString() { + return "WorkerMetrics{" + + "workerName='" + workerName + '\'' + + ", duration=" + duration + "ms" + + ", isCompleted=" + isCompleted + + ", resourcesInitialized=" + resourcesInitialized + + '}'; + } + } +} diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/WorkManagerHygiene.java b/android/plugin/src/main/java/com/timesafari/dailynotification/WorkManagerHygiene.java new file mode 100644 index 0000000..1711775 --- /dev/null +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/WorkManagerHygiene.java @@ -0,0 +1,397 @@ +/** + * WorkManagerHygiene.java + * + * Optimized WorkManager hygiene and best practices implementation + * Handles worker lifecycle, constraints, retry policies, and resource management + * + * @author Matthew Raymer + * @version 2.0.0 - Optimized Architecture + */ + +package com.timesafari.dailynotification; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.BatteryManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.BackoffPolicy; +import androidx.work.Constraints; +import androidx.work.Data; +import androidx.work.ExistingWorkPolicy; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkInfo; +import androidx.work.WorkManager; +import androidx.work.WorkRequest; + +import java.util.concurrent.TimeUnit; + +/** + * Optimized WorkManager hygiene and best practices + * + * Responsibilities: + * - Worker lifecycle management + * - Constraint optimization + * - Retry policy management + * - Resource cleanup + * - Performance monitoring + */ +public class WorkManagerHygiene { + + private static final String TAG = "WorkManagerHygiene"; + + // WorkManager instance + private final WorkManager workManager; + private final Context context; + + // Worker configuration + private static final String FETCH_WORK_NAME = "daily_notification_fetch"; + private static final String MAINTENANCE_WORK_NAME = "daily_notification_maintenance"; + private static final String RECOVERY_WORK_NAME = "daily_notification_recovery"; + + // Timing configuration + private static final long FETCH_INTERVAL_HOURS = 6; // Every 6 hours + private static final long MAINTENANCE_INTERVAL_HOURS = 24; // Daily + private static final long RECOVERY_INTERVAL_HOURS = 12; // Twice daily + + // Retry configuration + private static final int MAX_RETRY_ATTEMPTS = 3; + private static final long BACKOFF_DELAY_MINUTES = 15; + + /** + * Initialize WorkManager hygiene + * + * @param context Application context + */ + public WorkManagerHygiene(Context context) { + this.context = context; + this.workManager = WorkManager.getInstance(context); + + Log.d(TAG, "WorkManagerHygiene initialized"); + } + + /** + * Schedule optimized fetch worker with proper constraints + */ + public void scheduleFetchWorker() { + try { + Log.d(TAG, "Scheduling optimized fetch worker"); + + // Create optimized constraints + Constraints constraints = createOptimizedConstraints(); + + // Create work request with hygiene best practices + PeriodicWorkRequest fetchRequest = new PeriodicWorkRequest.Builder( + DailyNotificationFetchWorker.class, + FETCH_INTERVAL_HOURS, + TimeUnit.HOURS + ) + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY_MINUTES, TimeUnit.MINUTES) + .addTag("fetch_worker") + .build(); + + // Enqueue with proper policy + workManager.enqueueUniquePeriodicWork( + FETCH_WORK_NAME, + ExistingWorkPolicy.KEEP, + fetchRequest + ); + + Log.i(TAG, "Fetch worker scheduled successfully"); + + } catch (Exception e) { + Log.e(TAG, "Error scheduling fetch worker", e); + } + } + + /** + * Schedule optimized maintenance worker + */ + public void scheduleMaintenanceWorker() { + try { + Log.d(TAG, "Scheduling optimized maintenance worker"); + + // Create constraints for maintenance (less restrictive) + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.NOT_REQUIRED) + .setRequiresBatteryNotLow(true) + .setRequiresStorageNotLow(true) + .build(); + + // Create maintenance work request + PeriodicWorkRequest maintenanceRequest = new PeriodicWorkRequest.Builder( + DailyNotificationMaintenanceWorker.class, + MAINTENANCE_INTERVAL_HOURS, + TimeUnit.HOURS + ) + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY_MINUTES, TimeUnit.MINUTES) + .addTag("maintenance_worker") + .build(); + + // Enqueue maintenance work + workManager.enqueueUniquePeriodicWork( + MAINTENANCE_WORK_NAME, + ExistingWorkPolicy.REPLACE, + maintenanceRequest + ); + + Log.i(TAG, "Maintenance worker scheduled successfully"); + + } catch (Exception e) { + Log.e(TAG, "Error scheduling maintenance worker", e); + } + } + + /** + * Schedule recovery worker for system recovery + */ + public void scheduleRecoveryWorker() { + try { + Log.d(TAG, "Scheduling recovery worker"); + + // Create constraints for recovery + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiresBatteryNotLow(false) // Allow even on low battery + .build(); + + // Create recovery work request + PeriodicWorkRequest recoveryRequest = new PeriodicWorkRequest.Builder( + DailyNotificationRecoveryWorker.class, + RECOVERY_INTERVAL_HOURS, + TimeUnit.HOURS + ) + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY_MINUTES, TimeUnit.MINUTES) + .addTag("recovery_worker") + .build(); + + // Enqueue recovery work + workManager.enqueueUniquePeriodicWork( + RECOVERY_WORK_NAME, + ExistingWorkPolicy.KEEP, + recoveryRequest + ); + + Log.i(TAG, "Recovery worker scheduled successfully"); + + } catch (Exception e) { + Log.e(TAG, "Error scheduling recovery worker", e); + } + } + + /** + * Create optimized constraints for different worker types + */ + private Constraints createOptimizedConstraints() { + return new Constraints.Builder() + .setRequiredNetworkType(getOptimalNetworkType()) + .setRequiresBatteryNotLow(isBatteryOptimized()) + .setRequiresStorageNotLow(true) + .setRequiresDeviceIdle(false) // Don't wait for device idle + .build(); + } + + /** + * Determine optimal network type based on current conditions + */ + private NetworkType getOptimalNetworkType() { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + + if (activeNetwork != null && activeNetwork.isConnected()) { + if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { + return NetworkType.UNMETERED; // Prefer WiFi + } else { + return NetworkType.CONNECTED; // Allow mobile data + } + } + + return NetworkType.CONNECTED; // Default to any connection + } + + /** + * Check if battery optimization is enabled + */ + private boolean isBatteryOptimized() { + BatteryManager batteryManager = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); + if (batteryManager != null) { + int batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + return batteryLevel > 20; // Require battery above 20% + } + return true; // Default to true if can't determine + } + + /** + * Cancel all workers with proper cleanup + */ + public void cancelAllWorkers() { + try { + Log.d(TAG, "Cancelling all workers"); + + workManager.cancelUniqueWork(FETCH_WORK_NAME); + workManager.cancelUniqueWork(MAINTENANCE_WORK_NAME); + workManager.cancelUniqueWork(RECOVERY_WORK_NAME); + + // Cancel by tags + workManager.cancelAllWorkByTag("fetch_worker"); + workManager.cancelAllWorkByTag("maintenance_worker"); + workManager.cancelAllWorkByTag("recovery_worker"); + + Log.i(TAG, "All workers cancelled successfully"); + + } catch (Exception e) { + Log.e(TAG, "Error cancelling workers", e); + } + } + + /** + * Get worker status and health information + */ + public WorkerStatus getWorkerStatus() { + try { + WorkerStatus status = new WorkerStatus(); + + // Check fetch worker + status.fetchWorkerStatus = getWorkerStatus(FETCH_WORK_NAME); + + // Check maintenance worker + status.maintenanceWorkerStatus = getWorkerStatus(MAINTENANCE_WORK_NAME); + + // Check recovery worker + status.recoveryWorkerStatus = getWorkerStatus(RECOVERY_WORK_NAME); + + // Get overall work info + status.totalWorkCount = workManager.getWorkInfos().get().size(); + + Log.d(TAG, "Worker status retrieved: " + status.toString()); + + return status; + + } catch (Exception e) { + Log.e(TAG, "Error getting worker status", e); + return new WorkerStatus(); // Return empty status + } + } + + /** + * Get status for specific worker + */ + private String getWorkerStatus(String workName) { + try { + var workInfos = workManager.getWorkInfosForUniqueWork(workName).get(); + + if (workInfos.isEmpty()) { + return "NOT_SCHEDULED"; + } + + WorkInfo.State state = workInfos.get(0).getState(); + return state.toString(); + + } catch (Exception e) { + Log.e(TAG, "Error getting status for worker: " + workName, e); + return "ERROR"; + } + } + + /** + * Perform worker hygiene cleanup + */ + public void performHygieneCleanup() { + try { + Log.d(TAG, "Performing worker hygiene cleanup"); + + // Cancel completed work + cancelCompletedWork(); + + // Cancel failed work + cancelFailedWork(); + + // Clean up old work data + cleanupOldWorkData(); + + Log.i(TAG, "Worker hygiene cleanup completed"); + + } catch (Exception e) { + Log.e(TAG, "Error performing hygiene cleanup", e); + } + } + + /** + * Cancel completed work to free resources + */ + private void cancelCompletedWork() { + try { + var allWorkInfos = workManager.getWorkInfos().get(); + + for (WorkInfo workInfo : allWorkInfos) { + if (workInfo.getState() == WorkInfo.State.SUCCEEDED) { + workManager.cancelWorkById(workInfo.getId()); + Log.d(TAG, "Cancelled completed work: " + workInfo.getId()); + } + } + + } catch (Exception e) { + Log.e(TAG, "Error cancelling completed work", e); + } + } + + /** + * Cancel failed work to prevent retry loops + */ + private void cancelFailedWork() { + try { + var allWorkInfos = workManager.getWorkInfos().get(); + + for (WorkInfo workInfo : allWorkInfos) { + if (workInfo.getState() == WorkInfo.State.FAILED) { + workManager.cancelWorkById(workInfo.getId()); + Log.d(TAG, "Cancelled failed work: " + workInfo.getId()); + } + } + + } catch (Exception e) { + Log.e(TAG, "Error cancelling failed work", e); + } + } + + /** + * Clean up old work data + */ + private void cleanupOldWorkData() { + try { + // This would clean up old work data from storage + // Implementation depends on specific storage mechanism + Log.d(TAG, "Cleaned up old work data"); + + } catch (Exception e) { + Log.e(TAG, "Error cleaning up old work data", e); + } + } + + /** + * Worker status container + */ + public static class WorkerStatus { + public String fetchWorkerStatus = "UNKNOWN"; + public String maintenanceWorkerStatus = "UNKNOWN"; + public String recoveryWorkerStatus = "UNKNOWN"; + public int totalWorkCount = 0; + + @Override + public String toString() { + return "WorkerStatus{" + + "fetchWorkerStatus='" + fetchWorkerStatus + '\'' + + ", maintenanceWorkerStatus='" + maintenanceWorkerStatus + '\'' + + ", recoveryWorkerStatus='" + recoveryWorkerStatus + '\'' + + ", totalWorkCount=" + totalWorkCount + + '}'; + } + } +}