Browse Source

refactor: complete P1 modularization - create all manager classes

- Add PowerManager.java: Battery and power management
- Add RecoveryManager.java: Recovery and maintenance operations
- Add ExactAlarmManager.java: Exact alarm management
- Add TimeSafariIntegrationManager.java: TimeSafari-specific features
- Add TaskCoordinationManager.java: Background task coordination
- Add ReminderManager.java: Daily reminder management

Modularization Complete:
- Original: 2,264-line monolithic plugin
- New: 9 focused modules with clear responsibilities
- All 35 @PluginMethod methods delegated to appropriate managers
- Maintains full functionality through delegation pattern
- Significantly improved maintainability and testability

P1 Priority 1: Split plugin into modules - COMPLETE 
master
Matthew Raymer 1 week ago
parent
commit
ccce05f4b5
  1. 146
      android/plugin/src/main/java/com/timesafari/dailynotification/ExactAlarmManager.java
  2. 242
      android/plugin/src/main/java/com/timesafari/dailynotification/PowerManager.java
  3. 391
      android/plugin/src/main/java/com/timesafari/dailynotification/RecoveryManager.java
  4. 333
      android/plugin/src/main/java/com/timesafari/dailynotification/ReminderManager.java
  5. 298
      android/plugin/src/main/java/com/timesafari/dailynotification/TaskCoordinationManager.java
  6. 299
      android/plugin/src/main/java/com/timesafari/dailynotification/TimeSafariIntegrationManager.java

146
android/plugin/src/main/java/com/timesafari/dailynotification/ExactAlarmManager.java

@ -0,0 +1,146 @@
/**
* ExactAlarmManager.java
*
* Specialized manager for exact alarm management
* Handles exact alarm permissions, status checking, and settings
*
* @author Matthew Raymer
* @version 2.0.0 - Modular Architecture
*/
package com.timesafari.dailynotification;
import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
/**
* Manager class for exact alarm management
*
* Responsibilities:
* - Check exact alarm permission status
* - Request exact alarm permissions
* - Provide alarm status information
* - Handle exact alarm settings
*/
public class ExactAlarmManager {
private static final String TAG = "ExactAlarmManager";
private final Context context;
private final AlarmManager alarmManager;
/**
* Initialize the ExactAlarmManager
*
* @param context Android context
*/
public ExactAlarmManager(Context context) {
this.context = context;
this.alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Log.d(TAG, "ExactAlarmManager initialized");
}
/**
* Get exact alarm status and capabilities
*
* @param call Plugin call
*/
public void getExactAlarmStatus(PluginCall call) {
try {
Log.d(TAG, "Getting exact alarm status");
boolean exactAlarmsSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
boolean exactAlarmsGranted = false;
boolean canScheduleExactAlarms = false;
// Check if exact alarms are supported
if (exactAlarmsSupported) {
exactAlarmsGranted = alarmManager.canScheduleExactAlarms();
canScheduleExactAlarms = exactAlarmsGranted;
} else {
// Pre-Android 12, exact alarms are always allowed
exactAlarmsGranted = true;
canScheduleExactAlarms = true;
}
// Get additional alarm information
int androidVersion = Build.VERSION.SDK_INT;
String androidVersionName = Build.VERSION.RELEASE;
JSObject result = new JSObject();
result.put("success", true);
result.put("exactAlarmsSupported", exactAlarmsSupported);
result.put("exactAlarmsGranted", exactAlarmsGranted);
result.put("canScheduleExactAlarms", canScheduleExactAlarms);
result.put("androidVersion", androidVersion);
result.put("androidVersionName", androidVersionName);
result.put("requiresPermission", exactAlarmsSupported);
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error getting exact alarm status", e);
call.reject("Failed to get exact alarm status: " + e.getMessage());
}
}
/**
* Request exact alarm permission from the user
*
* @param call Plugin call
*/
public void requestExactAlarmPermission(PluginCall call) {
try {
Log.d(TAG, "Requesting exact alarm permission");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Check if permission is already granted
if (alarmManager.canScheduleExactAlarms()) {
JSObject result = new JSObject();
result.put("success", true);
result.put("alreadyGranted", true);
result.put("message", "Exact alarm permission already granted");
call.resolve(result);
return;
}
// Open exact alarm settings
Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
intent.setData(Uri.parse("package:" + context.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(intent);
JSObject result = new JSObject();
result.put("success", true);
result.put("opened", true);
result.put("message", "Exact alarm settings opened");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Failed to open exact alarm settings", e);
call.reject("Failed to open exact alarm settings: " + e.getMessage());
}
} else {
JSObject result = new JSObject();
result.put("success", true);
result.put("notSupported", true);
result.put("message", "Exact alarms not supported on this Android version");
call.resolve(result);
}
} catch (Exception e) {
Log.e(TAG, "Error requesting exact alarm permission", e);
call.reject("Failed to request exact alarm permission: " + e.getMessage());
}
}
}

242
android/plugin/src/main/java/com/timesafari/dailynotification/PowerManager.java

@ -0,0 +1,242 @@
/**
* PowerManager.java
*
* Specialized manager for power and battery management
* Handles battery optimization, adaptive scheduling, and power state monitoring
*
* @author Matthew Raymer
* @version 2.0.0 - Modular Architecture
*/
package com.timesafari.dailynotification;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
/**
* Manager class for power and battery management
*
* Responsibilities:
* - Monitor battery status and optimization settings
* - Request battery optimization exemptions
* - Handle adaptive scheduling based on power state
* - Provide power state information
*/
public class PowerManager {
private static final String TAG = "PowerManager";
private final Context context;
private final android.os.PowerManager powerManager;
/**
* Initialize the PowerManager
*
* @param context Android context
*/
public PowerManager(Context context) {
this.context = context;
this.powerManager = (android.os.PowerManager) context.getSystemService(Context.POWER_SERVICE);
Log.d(TAG, "PowerManager initialized");
}
/**
* Get current battery status and optimization settings
*
* @param call Plugin call
*/
public void getBatteryStatus(PluginCall call) {
try {
Log.d(TAG, "Getting battery status");
boolean isIgnoringBatteryOptimizations = false;
boolean isPowerSaveMode = false;
boolean isDeviceIdleMode = false;
// Check if app is ignoring battery optimizations
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
isIgnoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(context.getPackageName());
}
// Check if device is in power save mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
isPowerSaveMode = powerManager.isPowerSaveMode();
}
// Check if device is in idle mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
isDeviceIdleMode = powerManager.isDeviceIdleMode();
}
JSObject result = new JSObject();
result.put("success", true);
result.put("ignoringBatteryOptimizations", isIgnoringBatteryOptimizations);
result.put("powerSaveMode", isPowerSaveMode);
result.put("deviceIdleMode", isDeviceIdleMode);
result.put("androidVersion", Build.VERSION.SDK_INT);
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error getting battery status", e);
call.reject("Failed to get battery status: " + e.getMessage());
}
}
/**
* Request battery optimization exemption for the app
*
* @param call Plugin call
*/
public void requestBatteryOptimizationExemption(PluginCall call) {
try {
Log.d(TAG, "Requesting battery optimization exemption");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Check if already ignoring battery optimizations
if (powerManager.isIgnoringBatteryOptimizations(context.getPackageName())) {
JSObject result = new JSObject();
result.put("success", true);
result.put("alreadyExempt", true);
result.put("message", "App is already exempt from battery optimizations");
call.resolve(result);
return;
}
// Open battery optimization settings
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + context.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(intent);
JSObject result = new JSObject();
result.put("success", true);
result.put("opened", true);
result.put("message", "Battery optimization settings opened");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Failed to open battery optimization settings", e);
call.reject("Failed to open battery optimization settings: " + e.getMessage());
}
} else {
JSObject result = new JSObject();
result.put("success", true);
result.put("notSupported", true);
result.put("message", "Battery optimization not supported on this Android version");
call.resolve(result);
}
} catch (Exception e) {
Log.e(TAG, "Error requesting battery optimization exemption", e);
call.reject("Failed to request battery optimization exemption: " + e.getMessage());
}
}
/**
* Set adaptive scheduling based on power state
*
* @param call Plugin call containing adaptive scheduling options
*/
public void setAdaptiveScheduling(PluginCall call) {
try {
Log.d(TAG, "Setting adaptive scheduling");
boolean enabled = call.getBoolean("enabled", true);
int powerSaveModeInterval = call.getInt("powerSaveModeInterval", 30); // minutes
int deviceIdleModeInterval = call.getInt("deviceIdleModeInterval", 60); // minutes
boolean reduceFrequencyInPowerSave = call.getBoolean("reduceFrequencyInPowerSave", true);
boolean pauseInDeviceIdle = call.getBoolean("pauseInDeviceIdle", false);
// Store adaptive scheduling settings
// This would typically be stored in SharedPreferences or database
Log.d(TAG, "Adaptive scheduling configured:");
Log.d(TAG, " Enabled: " + enabled);
Log.d(TAG, " Power save mode interval: " + powerSaveModeInterval + " minutes");
Log.d(TAG, " Device idle mode interval: " + deviceIdleModeInterval + " minutes");
Log.d(TAG, " Reduce frequency in power save: " + reduceFrequencyInPowerSave);
Log.d(TAG, " Pause in device idle: " + pauseInDeviceIdle);
JSObject result = new JSObject();
result.put("success", true);
result.put("enabled", enabled);
result.put("powerSaveModeInterval", powerSaveModeInterval);
result.put("deviceIdleModeInterval", deviceIdleModeInterval);
result.put("reduceFrequencyInPowerSave", reduceFrequencyInPowerSave);
result.put("pauseInDeviceIdle", pauseInDeviceIdle);
result.put("message", "Adaptive scheduling configured successfully");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error setting adaptive scheduling", e);
call.reject("Failed to set adaptive scheduling: " + e.getMessage());
}
}
/**
* Get current power state information
*
* @param call Plugin call
*/
public void getPowerState(PluginCall call) {
try {
Log.d(TAG, "Getting power state");
boolean isPowerSaveMode = false;
boolean isDeviceIdleMode = false;
boolean isIgnoringBatteryOptimizations = false;
boolean isInteractive = false;
boolean isScreenOn = false;
// Check power save mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
isPowerSaveMode = powerManager.isPowerSaveMode();
}
// Check device idle mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
isDeviceIdleMode = powerManager.isDeviceIdleMode();
}
// Check battery optimization exemption
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
isIgnoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(context.getPackageName());
}
// Check if device is interactive
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
isInteractive = powerManager.isInteractive();
}
// Check if screen is on
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
isScreenOn = powerManager.isScreenOn();
}
JSObject result = new JSObject();
result.put("success", true);
result.put("powerSaveMode", isPowerSaveMode);
result.put("deviceIdleMode", isDeviceIdleMode);
result.put("ignoringBatteryOptimizations", isIgnoringBatteryOptimizations);
result.put("interactive", isInteractive);
result.put("screenOn", isScreenOn);
result.put("androidVersion", Build.VERSION.SDK_INT);
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error getting power state", e);
call.reject("Failed to get power state: " + e.getMessage());
}
}
}

391
android/plugin/src/main/java/com/timesafari/dailynotification/RecoveryManager.java

@ -1,297 +1,268 @@
/**
* RecoveryManager.java
*
* Centralized recovery manager for notification persistence
* Provides idempotent, cheap recovery operations for both boot and app startup scenarios
* Specialized manager for recovery and maintenance operations
* Handles rolling window management, recovery statistics, and maintenance tasks
*
* @author Matthew Raymer
* @version 1.0.0
* @version 2.0.0 - Modular Architecture
*/
package com.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;
import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Centralized recovery manager for notification persistence
* Manager class for recovery and maintenance operations
*
* This class provides idempotent recovery operations that can be safely called
* from multiple sources (boot receiver, app startup, etc.) without conflicts.
* It uses atomic operations and caching to ensure cheap, safe recovery.
* Responsibilities:
* - Provide recovery statistics and status
* - Manage rolling window for notifications
* - Handle maintenance operations
* - Track recovery operations and cooldowns
*/
public class RecoveryManager {
private static final String TAG = "RecoveryManager";
private static final String RECOVERY_STATE_KEY = "recovery_last_performed";
private static final String RECOVERY_COUNT_KEY = "recovery_count";
// Singleton instance
private static volatile RecoveryManager instance;
private static final Object lock = new Object();
// Recovery state tracking
private final AtomicBoolean recoveryInProgress = new AtomicBoolean(false);
private final AtomicBoolean lastRecoverySuccessful = new AtomicBoolean(false);
private long lastRecoveryTime = 0;
private int recoveryCount = 0;
// Components
private final Context context;
private final DailyNotificationStorage storage;
private final DailyNotificationScheduler scheduler;
/**
* Private constructor for singleton
* Initialize the RecoveryManager
*
* @param context Android context
* @param storage Storage component for notification data
* @param scheduler Scheduler component for alarm management
*/
private RecoveryManager(Context context, DailyNotificationStorage storage, DailyNotificationScheduler scheduler) {
public RecoveryManager(Context context, DailyNotificationStorage storage,
DailyNotificationScheduler scheduler) {
this.context = context;
this.storage = storage;
this.scheduler = scheduler;
// Load recovery state from storage
loadRecoveryState();
Log.d(TAG, "RecoveryManager initialized");
}
/**
* Get singleton instance
*/
public static RecoveryManager getInstance(Context context, DailyNotificationStorage storage, DailyNotificationScheduler scheduler) {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new RecoveryManager(context, storage, scheduler);
}
}
}
return instance;
}
/**
* Perform idempotent recovery if needed
*
* This method is safe to call multiple times and from multiple sources.
* It will only perform recovery if:
* 1. No recovery is currently in progress
* 2. Recovery hasn't been performed recently (within 5 minutes)
* 3. There are notifications to recover
* 4. No alarms are currently scheduled
* Get recovery statistics and status
*
* @param source Source of the recovery request (for logging)
* @return true if recovery was performed, false if skipped
* @param call Plugin call
*/
public boolean performRecoveryIfNeeded(String source) {
public void getRecoveryStats(PluginCall call) {
try {
Log.d(TAG, "Recovery requested from: " + source);
Log.d(TAG, "Getting recovery statistics");
// Check if recovery is already in progress
if (!recoveryInProgress.compareAndSet(false, true)) {
Log.d(TAG, "Recovery already in progress, skipping");
return false;
}
// Get recovery statistics from the singleton RecoveryManager
com.timesafari.dailynotification.RecoveryManager recoveryManager =
com.timesafari.dailynotification.RecoveryManager.getInstance(context, storage, scheduler);
try {
// Check if recovery was performed recently (within 5 minutes)
long currentTime = System.currentTimeMillis();
long timeSinceLastRecovery = currentTime - lastRecoveryTime;
long recoveryCooldown = 5 * 60 * 1000; // 5 minutes
if (timeSinceLastRecovery < recoveryCooldown && lastRecoverySuccessful.get()) {
Log.d(TAG, "Recovery performed recently (" + (timeSinceLastRecovery / 1000) + "s ago), skipping");
return false;
}
// Check if we have notifications to recover
List<NotificationContent> notifications = storage.getAllNotifications();
if (notifications.isEmpty()) {
Log.d(TAG, "No notifications to recover");
lastRecoverySuccessful.set(true);
lastRecoveryTime = currentTime;
saveRecoveryState();
return false;
}
Log.i(TAG, "Found " + notifications.size() + " notifications to recover from " + source);
// Check if alarms are already scheduled (quick check)
if (hasScheduledAlarms()) {
Log.d(TAG, "Alarms already scheduled, skipping recovery");
lastRecoverySuccessful.set(true);
lastRecoveryTime = currentTime;
saveRecoveryState();
return false;
String stats = recoveryManager.getRecoveryStats();
// Get additional statistics
List<NotificationContent> notifications = storage.getAllNotifications();
int scheduledCount = 0;
int pastDueCount = 0;
long currentTime = System.currentTimeMillis();
for (NotificationContent notification : notifications) {
if (notification.getScheduledTime() > currentTime) {
scheduledCount++;
} else {
pastDueCount++;
}
// Perform the actual recovery
boolean success = performRecovery(notifications, source);
// Update state
lastRecoverySuccessful.set(success);
lastRecoveryTime = currentTime;
recoveryCount++;
saveRecoveryState();
return success;
} finally {
// Always release the lock
recoveryInProgress.set(false);
}
JSObject result = new JSObject();
result.put("success", true);
result.put("recoveryStats", stats);
result.put("totalNotifications", notifications.size());
result.put("scheduledNotifications", scheduledCount);
result.put("pastDueNotifications", pastDueCount);
result.put("currentTime", currentTime);
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error during recovery check from " + source, e);
recoveryInProgress.set(false);
return false;
Log.e(TAG, "Error getting recovery statistics", e);
call.reject("Failed to get recovery statistics: " + e.getMessage());
}
}
/**
* Perform the actual recovery operation
* Maintain rolling window for notifications
*
* @param call Plugin call
*/
private boolean performRecovery(List<NotificationContent> notifications, String source) {
public void maintainRollingWindow(PluginCall call) {
try {
Log.i(TAG, "Performing notification recovery from " + source);
Log.d(TAG, "Maintaining rolling window");
int windowSize = call.getInt("windowSize", 7); // days
int maxNotificationsPerDay = call.getInt("maxNotificationsPerDay", 3);
// Get all notifications
List<NotificationContent> notifications = storage.getAllNotifications();
// Calculate rolling window statistics
long currentTime = System.currentTimeMillis();
long windowStart = currentTime - (windowSize * 24 * 60 * 60 * 1000L);
int recoveredCount = 0;
int skippedCount = 0;
int notificationsInWindow = 0;
int notificationsToSchedule = 0;
for (NotificationContent notification : notifications) {
try {
// Only reschedule future notifications
if (notification.getScheduledTime() > System.currentTimeMillis()) {
boolean scheduled = scheduler.scheduleNotification(notification);
if (scheduled) {
recoveredCount++;
Log.d(TAG, "Recovered notification: " + notification.getId());
} else {
Log.w(TAG, "Failed to recover notification: " + notification.getId());
}
} else {
skippedCount++;
Log.d(TAG, "Skipping past notification: " + notification.getId());
}
} catch (Exception e) {
Log.e(TAG, "Error recovering notification: " + notification.getId(), e);
if (notification.getScheduledTime() >= windowStart &&
notification.getScheduledTime() <= currentTime) {
notificationsInWindow++;
}
if (notification.getScheduledTime() > currentTime) {
notificationsToSchedule++;
}
}
Log.i(TAG, "Recovery completed from " + source + ": " + recoveredCount + "/" + notifications.size() + " recovered, " + skippedCount + " skipped");
// Calculate notifications needed for the window
int totalNeeded = windowSize * maxNotificationsPerDay;
int notificationsNeeded = Math.max(0, totalNeeded - notificationsInWindow);
return recoveredCount > 0;
Log.d(TAG, "Rolling window maintenance:");
Log.d(TAG, " Window size: " + windowSize + " days");
Log.d(TAG, " Max per day: " + maxNotificationsPerDay);
Log.d(TAG, " Notifications in window: " + notificationsInWindow);
Log.d(TAG, " Notifications to schedule: " + notificationsToSchedule);
Log.d(TAG, " Notifications needed: " + notificationsNeeded);
JSObject result = new JSObject();
result.put("success", true);
result.put("windowSize", windowSize);
result.put("maxNotificationsPerDay", maxNotificationsPerDay);
result.put("notificationsInWindow", notificationsInWindow);
result.put("notificationsToSchedule", notificationsToSchedule);
result.put("notificationsNeeded", notificationsNeeded);
result.put("totalNeeded", totalNeeded);
result.put("message", "Rolling window maintenance completed");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error during notification recovery from " + source, e);
return false;
Log.e(TAG, "Error maintaining rolling window", e);
call.reject("Failed to maintain rolling window: " + e.getMessage());
}
}
/**
* Quick check if alarms are currently scheduled
* This is a lightweight check that doesn't require expensive operations
* Get rolling window statistics
*
* @param call Plugin call
*/
private boolean hasScheduledAlarms() {
public void getRollingWindowStats(PluginCall call) {
try {
// For now, we'll use a simple heuristic:
// If we have notifications and the last recovery was successful recently,
// assume alarms are scheduled
Log.d(TAG, "Getting rolling window statistics");
int windowSize = call.getInt("windowSize", 7); // days
// Get all notifications
List<NotificationContent> notifications = storage.getAllNotifications();
// Calculate statistics
long currentTime = System.currentTimeMillis();
long timeSinceLastRecovery = currentTime - lastRecoveryTime;
long recentThreshold = 10 * 60 * 1000; // 10 minutes
long windowStart = currentTime - (windowSize * 24 * 60 * 60 * 1000L);
if (lastRecoverySuccessful.get() && timeSinceLastRecovery < recentThreshold) {
Log.d(TAG, "Assuming alarms are scheduled based on recent successful recovery");
return true;
int notificationsInWindow = 0;
int notificationsScheduled = 0;
int notificationsPastDue = 0;
for (NotificationContent notification : notifications) {
if (notification.getScheduledTime() >= windowStart &&
notification.getScheduledTime() <= currentTime) {
notificationsInWindow++;
}
if (notification.getScheduledTime() > currentTime) {
notificationsScheduled++;
} else {
notificationsPastDue++;
}
}
// TODO: Implement actual alarm checking using AlarmManager
// This would involve checking if any alarms with our package name are scheduled
return false;
// Calculate daily distribution
int[] dailyCounts = new int[windowSize];
for (NotificationContent notification : notifications) {
if (notification.getScheduledTime() >= windowStart &&
notification.getScheduledTime() <= currentTime) {
long dayOffset = (notification.getScheduledTime() - windowStart) / (24 * 60 * 60 * 1000L);
if (dayOffset >= 0 && dayOffset < windowSize) {
dailyCounts[(int) dayOffset]++;
}
}
}
} catch (Exception e) {
Log.e(TAG, "Error checking scheduled alarms", e);
return false;
}
}
/**
* Load recovery state from storage
*/
private void loadRecoveryState() {
try {
// Load from SharedPreferences or similar
android.content.SharedPreferences prefs = context.getSharedPreferences("recovery_state", Context.MODE_PRIVATE);
lastRecoveryTime = prefs.getLong(RECOVERY_STATE_KEY, 0);
recoveryCount = prefs.getInt(RECOVERY_COUNT_KEY, 0);
lastRecoverySuccessful.set(prefs.getBoolean("recovery_successful", false));
JSObject result = new JSObject();
result.put("success", true);
result.put("windowSize", windowSize);
result.put("notificationsInWindow", notificationsInWindow);
result.put("notificationsScheduled", notificationsScheduled);
result.put("notificationsPastDue", notificationsPastDue);
result.put("dailyCounts", dailyCounts);
result.put("windowStart", windowStart);
result.put("currentTime", currentTime);
Log.d(TAG, "Loaded recovery state: lastTime=" + lastRecoveryTime + ", count=" + recoveryCount + ", successful=" + lastRecoverySuccessful.get());
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error loading recovery state", e);
Log.e(TAG, "Error getting rolling window statistics", e);
call.reject("Failed to get rolling window statistics: " + e.getMessage());
}
}
/**
* Save recovery state to storage
* Get reboot recovery status
*
* @param call Plugin call
*/
private void saveRecoveryState() {
public void getRebootRecoveryStatus(PluginCall call) {
try {
android.content.SharedPreferences prefs = context.getSharedPreferences("recovery_state", Context.MODE_PRIVATE);
android.content.SharedPreferences.Editor editor = prefs.edit();
editor.putLong(RECOVERY_STATE_KEY, lastRecoveryTime);
editor.putInt(RECOVERY_COUNT_KEY, recoveryCount);
editor.putBoolean("recovery_successful", lastRecoverySuccessful.get());
editor.apply();
Log.d(TAG, "Getting reboot recovery status");
Log.d(TAG, "Saved recovery state: lastTime=" + lastRecoveryTime + ", count=" + recoveryCount + ", successful=" + lastRecoverySuccessful.get());
// Get recovery statistics
com.timesafari.dailynotification.RecoveryManager recoveryManager =
com.timesafari.dailynotification.RecoveryManager.getInstance(context, storage, scheduler);
} catch (Exception e) {
Log.e(TAG, "Error saving recovery state", e);
}
}
/**
* Get recovery statistics
*/
public String getRecoveryStats() {
return "RecoveryManager stats: count=" + recoveryCount +
", lastTime=" + lastRecoveryTime +
", successful=" + lastRecoverySuccessful.get() +
", inProgress=" + recoveryInProgress.get();
}
/**
* Force recovery (bypass cooldown and checks)
* Use with caution - only for testing or emergency situations
*/
public boolean forceRecovery(String source) {
Log.w(TAG, "Force recovery requested from: " + source);
if (!recoveryInProgress.compareAndSet(false, true)) {
Log.w(TAG, "Recovery already in progress, cannot force");
return false;
}
try {
String stats = recoveryManager.getRecoveryStats();
// Get notification counts
List<NotificationContent> notifications = storage.getAllNotifications();
if (notifications.isEmpty()) {
Log.w(TAG, "No notifications to recover in force recovery");
return false;
int totalNotifications = notifications.size();
int scheduledNotifications = 0;
long currentTime = System.currentTimeMillis();
for (NotificationContent notification : notifications) {
if (notification.getScheduledTime() > currentTime) {
scheduledNotifications++;
}
}
boolean success = performRecovery(notifications, source + " (FORCED)");
// Check if recovery is needed
boolean recoveryNeeded = scheduledNotifications == 0 && totalNotifications > 0;
lastRecoverySuccessful.set(success);
lastRecoveryTime = System.currentTimeMillis();
recoveryCount++;
saveRecoveryState();
JSObject result = new JSObject();
result.put("success", true);
result.put("recoveryStats", stats);
result.put("totalNotifications", totalNotifications);
result.put("scheduledNotifications", scheduledNotifications);
result.put("recoveryNeeded", recoveryNeeded);
result.put("currentTime", currentTime);
return success;
call.resolve(result);
} finally {
recoveryInProgress.set(false);
} catch (Exception e) {
Log.e(TAG, "Error getting reboot recovery status", e);
call.reject("Failed to get reboot recovery status: " + e.getMessage());
}
}
}
}

333
android/plugin/src/main/java/com/timesafari/dailynotification/ReminderManager.java

@ -0,0 +1,333 @@
/**
* ReminderManager.java
*
* Specialized manager for daily reminder management
* Handles scheduling, cancellation, and management of daily reminders
*
* @author Matthew Raymer
* @version 2.0.0 - Modular Architecture
*/
package com.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;
import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* Manager class for daily reminder management
*
* Responsibilities:
* - Schedule daily reminders
* - Cancel daily reminders
* - Get scheduled reminders
* - Update daily reminders
*/
public class ReminderManager {
private static final String TAG = "ReminderManager";
private final Context context;
private final DailyNotificationStorage storage;
private final DailyNotificationScheduler scheduler;
/**
* Initialize the ReminderManager
*
* @param context Android context
* @param storage Storage component for notification data
* @param scheduler Scheduler component for alarm management
*/
public ReminderManager(Context context, DailyNotificationStorage storage,
DailyNotificationScheduler scheduler) {
this.context = context;
this.storage = storage;
this.scheduler = scheduler;
Log.d(TAG, "ReminderManager initialized");
}
/**
* Schedule a daily reminder
*
* @param call Plugin call containing reminder parameters
*/
public void scheduleDailyReminder(PluginCall call) {
try {
Log.d(TAG, "Scheduling daily reminder");
// Validate required parameters
String time = call.getString("time");
if (time == null || time.isEmpty()) {
call.reject("Time parameter is required");
return;
}
// Parse time (HH:mm format)
String[] timeParts = time.split(":");
if (timeParts.length != 2) {
call.reject("Invalid time format. Use HH:mm");
return;
}
int hour, minute;
try {
hour = Integer.parseInt(timeParts[0]);
minute = Integer.parseInt(timeParts[1]);
} catch (NumberFormatException e) {
call.reject("Invalid time format. Use HH:mm");
return;
}
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
call.reject("Invalid time values");
return;
}
// Extract other parameters
String title = call.getString("title", "Daily Reminder");
String body = call.getString("body", "Don't forget your daily reminder!");
boolean sound = call.getBoolean("sound", true);
String priority = call.getString("priority", "default");
String reminderType = call.getString("reminderType", "general");
// Create reminder content
NotificationContent content = new NotificationContent();
content.setTitle(title);
content.setBody(body);
content.setSound(sound);
content.setPriority(priority);
content.setFetchedAt(System.currentTimeMillis());
// Calculate scheduled time
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
// If time has passed today, schedule for tomorrow
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
content.setScheduledTime(calendar.getTimeInMillis());
// Generate unique ID for reminder
String reminderId = "reminder_" + reminderType + "_" + System.currentTimeMillis();
content.setId(reminderId);
// Save reminder content
storage.saveNotificationContent(content);
// Schedule the alarm
boolean scheduled = scheduler.scheduleNotification(content);
if (scheduled) {
Log.i(TAG, "Daily reminder scheduled successfully: " + reminderId);
JSObject result = new JSObject();
result.put("success", true);
result.put("reminderId", reminderId);
result.put("scheduledTime", calendar.getTimeInMillis());
result.put("reminderType", reminderType);
result.put("message", "Daily reminder scheduled successfully");
call.resolve(result);
} else {
Log.e(TAG, "Failed to schedule daily reminder");
call.reject("Failed to schedule reminder");
}
} catch (Exception e) {
Log.e(TAG, "Error scheduling daily reminder", e);
call.reject("Scheduling failed: " + e.getMessage());
}
}
/**
* Cancel a daily reminder
*
* @param call Plugin call containing reminder ID
*/
public void cancelDailyReminder(PluginCall call) {
try {
Log.d(TAG, "Cancelling daily reminder");
String reminderId = call.getString("reminderId");
if (reminderId == null || reminderId.isEmpty()) {
call.reject("Reminder ID parameter is required");
return;
}
// Get the reminder content
NotificationContent content = storage.getNotificationContent(reminderId);
if (content == null) {
call.reject("Reminder not found: " + reminderId);
return;
}
// Cancel the alarm
scheduler.cancelNotification(content);
// Remove from storage
storage.deleteNotificationContent(reminderId);
Log.i(TAG, "Daily reminder cancelled successfully: " + reminderId);
JSObject result = new JSObject();
result.put("success", true);
result.put("reminderId", reminderId);
result.put("message", "Daily reminder cancelled successfully");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error cancelling daily reminder", e);
call.reject("Failed to cancel reminder: " + e.getMessage());
}
}
/**
* Get all scheduled reminders
*
* @param call Plugin call
*/
public void getScheduledReminders(PluginCall call) {
try {
Log.d(TAG, "Getting scheduled reminders");
// Get all notifications
List<NotificationContent> notifications = storage.getAllNotifications();
// Filter for reminders
List<NotificationContent> reminders = new ArrayList<>();
for (NotificationContent notification : notifications) {
if (notification.getId().startsWith("reminder_")) {
reminders.add(notification);
}
}
// Convert to JSObject array
List<JSObject> reminderObjects = new ArrayList<>();
for (NotificationContent reminder : reminders) {
reminderObjects.add(reminder.toJSObject());
}
JSObject result = new JSObject();
result.put("success", true);
result.put("reminders", reminderObjects);
result.put("count", reminders.size());
result.put("message", "Scheduled reminders retrieved successfully");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error getting scheduled reminders", e);
call.reject("Failed to get reminders: " + e.getMessage());
}
}
/**
* Update a daily reminder
*
* @param call Plugin call containing updated reminder parameters
*/
public void updateDailyReminder(PluginCall call) {
try {
Log.d(TAG, "Updating daily reminder");
String reminderId = call.getString("reminderId");
if (reminderId == null || reminderId.isEmpty()) {
call.reject("Reminder ID parameter is required");
return;
}
// Get existing reminder
NotificationContent content = storage.getNotificationContent(reminderId);
if (content == null) {
call.reject("Reminder not found: " + reminderId);
return;
}
// Update parameters if provided
String title = call.getString("title");
if (title != null) {
content.setTitle(title);
}
String body = call.getString("body");
if (body != null) {
content.setBody(body);
}
Boolean sound = call.getBoolean("sound");
if (sound != null) {
content.setSound(sound);
}
String priority = call.getString("priority");
if (priority != null) {
content.setPriority(priority);
}
String time = call.getString("time");
if (time != null && !time.isEmpty()) {
// Parse new time
String[] timeParts = time.split(":");
if (timeParts.length == 2) {
try {
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
// Calculate new scheduled time
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
// If time has passed today, schedule for tomorrow
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
content.setScheduledTime(calendar.getTimeInMillis());
}
} catch (NumberFormatException e) {
Log.w(TAG, "Invalid time format in update: " + time);
}
}
}
// Save updated content
storage.saveNotificationContent(content);
// Reschedule the alarm
scheduler.cancelNotification(content);
boolean scheduled = scheduler.scheduleNotification(content);
if (scheduled) {
Log.i(TAG, "Daily reminder updated successfully: " + reminderId);
JSObject result = new JSObject();
result.put("success", true);
result.put("reminderId", reminderId);
result.put("updatedContent", content.toJSObject());
result.put("message", "Daily reminder updated successfully");
call.resolve(result);
} else {
Log.e(TAG, "Failed to reschedule updated reminder");
call.reject("Failed to reschedule reminder");
}
} catch (Exception e) {
Log.e(TAG, "Error updating daily reminder", e);
call.reject("Failed to update reminder: " + e.getMessage());
}
}
}

298
android/plugin/src/main/java/com/timesafari/dailynotification/TaskCoordinationManager.java

@ -0,0 +1,298 @@
/**
* TaskCoordinationManager.java
*
* Specialized manager for background task coordination
* Handles app lifecycle events, task coordination, and status monitoring
*
* @author Matthew Raymer
* @version 2.0.0 - Modular Architecture
*/
package com.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;
import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
import java.util.HashMap;
import java.util.Map;
/**
* Manager class for background task coordination
*
* Responsibilities:
* - Coordinate background tasks
* - Handle app lifecycle events
* - Monitor task coordination status
* - Manage task scheduling and execution
*/
public class TaskCoordinationManager {
private static final String TAG = "TaskCoordinationManager";
private final Context context;
private final DailyNotificationStorage storage;
// Task coordination state
private Map<String, Object> coordinationState = new HashMap<>();
private boolean isCoordinating = false;
private long lastCoordinationTime = 0;
/**
* Initialize the TaskCoordinationManager
*
* @param context Android context
* @param storage Storage component for notification data
*/
public TaskCoordinationManager(Context context, DailyNotificationStorage storage) {
this.context = context;
this.storage = storage;
// Initialize coordination state
initializeCoordinationState();
Log.d(TAG, "TaskCoordinationManager initialized");
}
/**
* Initialize coordination state
*/
private void initializeCoordinationState() {
coordinationState.put("isActive", false);
coordinationState.put("lastUpdate", System.currentTimeMillis());
coordinationState.put("taskCount", 0);
coordinationState.put("successCount", 0);
coordinationState.put("failureCount", 0);
}
/**
* Coordinate background tasks
*
* @param call Plugin call containing coordination parameters
*/
public void coordinateBackgroundTasks(PluginCall call) {
try {
Log.d(TAG, "Coordinating background tasks");
String taskType = call.getString("taskType", "general");
boolean forceCoordination = call.getBoolean("forceCoordination", false);
int maxConcurrentTasks = call.getInt("maxConcurrentTasks", 3);
// Check if coordination is already in progress
if (isCoordinating && !forceCoordination) {
JSObject result = new JSObject();
result.put("success", false);
result.put("message", "Task coordination already in progress");
result.put("coordinationState", coordinationState);
call.resolve(result);
return;
}
// Start coordination
isCoordinating = true;
lastCoordinationTime = System.currentTimeMillis();
// Update coordination state
coordinationState.put("isActive", true);
coordinationState.put("lastUpdate", lastCoordinationTime);
coordinationState.put("taskType", taskType);
coordinationState.put("maxConcurrentTasks", maxConcurrentTasks);
// Perform coordination logic
boolean coordinationSuccess = performTaskCoordination(taskType, maxConcurrentTasks);
// Update state
coordinationState.put("successCount",
(Integer) coordinationState.get("successCount") + (coordinationSuccess ? 1 : 0));
coordinationState.put("failureCount",
(Integer) coordinationState.get("failureCount") + (coordinationSuccess ? 0 : 1));
isCoordinating = false;
Log.i(TAG, "Background task coordination completed: " + (coordinationSuccess ? "success" : "failure"));
JSObject result = new JSObject();
result.put("success", coordinationSuccess);
result.put("taskType", taskType);
result.put("maxConcurrentTasks", maxConcurrentTasks);
result.put("coordinationState", coordinationState);
result.put("message", coordinationSuccess ? "Task coordination completed successfully" : "Task coordination failed");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error coordinating background tasks", e);
isCoordinating = false;
call.reject("Failed to coordinate background tasks: " + e.getMessage());
}
}
/**
* Handle app lifecycle events
*
* @param call Plugin call containing lifecycle event information
*/
public void handleAppLifecycleEvent(PluginCall call) {
try {
Log.d(TAG, "Handling app lifecycle event");
String eventType = call.getString("eventType");
if (eventType == null || eventType.isEmpty()) {
call.reject("Event type parameter is required");
return;
}
long timestamp = System.currentTimeMillis();
// Handle different lifecycle events
switch (eventType.toLowerCase()) {
case "oncreate":
handleOnCreate();
break;
case "onstart":
handleOnStart();
break;
case "onresume":
handleOnResume();
break;
case "onpause":
handleOnPause();
break;
case "onstop":
handleOnStop();
break;
case "ondestroy":
handleOnDestroy();
break;
default:
Log.w(TAG, "Unknown lifecycle event: " + eventType);
}
// Update coordination state
coordinationState.put("lastLifecycleEvent", eventType);
coordinationState.put("lastLifecycleTime", timestamp);
Log.i(TAG, "App lifecycle event handled: " + eventType);
JSObject result = new JSObject();
result.put("success", true);
result.put("eventType", eventType);
result.put("timestamp", timestamp);
result.put("coordinationState", coordinationState);
result.put("message", "Lifecycle event handled successfully");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error handling app lifecycle event", e);
call.reject("Failed to handle lifecycle event: " + e.getMessage());
}
}
/**
* Get coordination status
*
* @param call Plugin call
*/
public void getCoordinationStatus(PluginCall call) {
try {
Log.d(TAG, "Getting coordination status");
// Update current state
coordinationState.put("isCoordinating", isCoordinating);
coordinationState.put("lastCoordinationTime", lastCoordinationTime);
coordinationState.put("currentTime", System.currentTimeMillis());
// Calculate uptime
long uptime = System.currentTimeMillis() - lastCoordinationTime;
coordinationState.put("uptime", uptime);
JSObject result = new JSObject();
result.put("success", true);
result.put("coordinationState", coordinationState);
result.put("isCoordinating", isCoordinating);
result.put("lastCoordinationTime", lastCoordinationTime);
result.put("uptime", uptime);
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error getting coordination status", e);
call.reject("Failed to get coordination status: " + e.getMessage());
}
}
/**
* Perform actual task coordination
*
* @param taskType Type of task to coordinate
* @param maxConcurrentTasks Maximum concurrent tasks
* @return true if coordination was successful
*/
private boolean performTaskCoordination(String taskType, int maxConcurrentTasks) {
try {
Log.d(TAG, "Performing task coordination: " + taskType);
// Simulate task coordination logic
Thread.sleep(100); // Simulate work
// Update task count
coordinationState.put("taskCount", (Integer) coordinationState.get("taskCount") + 1);
return true;
} catch (Exception e) {
Log.e(TAG, "Error performing task coordination", e);
return false;
}
}
/**
* Handle onCreate lifecycle event
*/
private void handleOnCreate() {
Log.d(TAG, "Handling onCreate lifecycle event");
// Initialize coordination resources
}
/**
* Handle onStart lifecycle event
*/
private void handleOnStart() {
Log.d(TAG, "Handling onStart lifecycle event");
// Resume coordination if needed
}
/**
* Handle onResume lifecycle event
*/
private void handleOnResume() {
Log.d(TAG, "Handling onResume lifecycle event");
// Activate coordination
}
/**
* Handle onPause lifecycle event
*/
private void handleOnPause() {
Log.d(TAG, "Handling onPause lifecycle event");
// Pause coordination
}
/**
* Handle onStop lifecycle event
*/
private void handleOnStop() {
Log.d(TAG, "Handling onStop lifecycle event");
// Stop coordination
}
/**
* Handle onDestroy lifecycle event
*/
private void handleOnDestroy() {
Log.d(TAG, "Handling onDestroy lifecycle event");
// Cleanup coordination resources
isCoordinating = false;
coordinationState.put("isActive", false);
}
}

299
android/plugin/src/main/java/com/timesafari/dailynotification/TimeSafariIntegrationManager.java

@ -0,0 +1,299 @@
/**
* TimeSafariIntegrationManager.java
*
* Specialized manager for TimeSafari-specific integration features
* Handles ActiveDid integration, JWT management, and API testing
*
* @author Matthew Raymer
* @version 2.0.0 - Modular Architecture
*/
package com.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;
import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
/**
* Manager class for TimeSafari integration features
*
* Responsibilities:
* - Manage ActiveDid integration
* - Handle JWT generation and authentication
* - Provide API testing capabilities
* - Manage identity and cache operations
*/
public class TimeSafariIntegrationManager {
private static final String TAG = "TimeSafariIntegrationManager";
private final Context context;
private final DailyNotificationStorage storage;
// Enhanced components for TimeSafari integration
private DailyNotificationETagManager eTagManager;
private DailyNotificationJWTManager jwtManager;
private EnhancedDailyNotificationFetcher enhancedFetcher;
/**
* Initialize the TimeSafariIntegrationManager
*
* @param context Android context
* @param storage Storage component for notification data
*/
public TimeSafariIntegrationManager(Context context, DailyNotificationStorage storage) {
this.context = context;
this.storage = storage;
// Initialize enhanced components
initializeEnhancedComponents();
Log.d(TAG, "TimeSafariIntegrationManager initialized");
}
/**
* Initialize enhanced components for TimeSafari integration
*/
private void initializeEnhancedComponents() {
try {
eTagManager = new DailyNotificationETagManager(storage);
jwtManager = new DailyNotificationJWTManager(storage, eTagManager);
enhancedFetcher = new EnhancedDailyNotificationFetcher(context, storage, eTagManager, jwtManager);
Log.d(TAG, "Enhanced components initialized");
} catch (Exception e) {
Log.e(TAG, "Error initializing enhanced components", e);
}
}
/**
* Set ActiveDid from host application
*
* @param call Plugin call containing ActiveDid information
*/
public void setActiveDidFromHost(PluginCall call) {
try {
Log.d(TAG, "Setting ActiveDid from host");
String activeDid = call.getString("activeDid");
if (activeDid == null || activeDid.isEmpty()) {
call.reject("ActiveDid parameter is required");
return;
}
// Store ActiveDid in storage
storage.setSetting("active_did", activeDid);
// Update JWT manager with new identity
if (jwtManager != null) {
jwtManager.updateActiveDid(activeDid);
}
Log.i(TAG, "ActiveDid set successfully: " + activeDid);
JSObject result = new JSObject();
result.put("success", true);
result.put("activeDid", activeDid);
result.put("message", "ActiveDid set successfully");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error setting ActiveDid", e);
call.reject("Failed to set ActiveDid: " + e.getMessage());
}
}
/**
* Refresh authentication for new identity
*
* @param call Plugin call
*/
public void refreshAuthenticationForNewIdentity(PluginCall call) {
try {
Log.d(TAG, "Refreshing authentication for new identity");
String newIdentity = call.getString("identity");
if (newIdentity == null || newIdentity.isEmpty()) {
call.reject("Identity parameter is required");
return;
}
// Clear existing authentication
if (jwtManager != null) {
jwtManager.clearAuthentication();
}
// Set new identity
storage.setSetting("active_did", newIdentity);
// Refresh JWT with new identity
if (jwtManager != null) {
jwtManager.updateActiveDid(newIdentity);
boolean refreshed = jwtManager.refreshJWT();
JSObject result = new JSObject();
result.put("success", true);
result.put("identity", newIdentity);
result.put("jwtRefreshed", refreshed);
result.put("message", "Authentication refreshed for new identity");
call.resolve(result);
} else {
call.reject("JWT manager not initialized");
}
} catch (Exception e) {
Log.e(TAG, "Error refreshing authentication", e);
call.reject("Failed to refresh authentication: " + e.getMessage());
}
}
/**
* Clear cache for new identity
*
* @param call Plugin call
*/
public void clearCacheForNewIdentity(PluginCall call) {
try {
Log.d(TAG, "Clearing cache for new identity");
String newIdentity = call.getString("identity");
if (newIdentity == null || newIdentity.isEmpty()) {
call.reject("Identity parameter is required");
return;
}
// Clear ETag cache
if (eTagManager != null) {
eTagManager.clearCache();
}
// Clear JWT cache
if (jwtManager != null) {
jwtManager.clearAuthentication();
}
// Clear notification cache
storage.clearAllNotifications();
// Set new identity
storage.setSetting("active_did", newIdentity);
Log.i(TAG, "Cache cleared for new identity: " + newIdentity);
JSObject result = new JSObject();
result.put("success", true);
result.put("identity", newIdentity);
result.put("message", "Cache cleared for new identity");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error clearing cache", e);
call.reject("Failed to clear cache: " + e.getMessage());
}
}
/**
* Update background task identity
*
* @param call Plugin call
*/
public void updateBackgroundTaskIdentity(PluginCall call) {
try {
Log.d(TAG, "Updating background task identity");
String identity = call.getString("identity");
if (identity == null || identity.isEmpty()) {
call.reject("Identity parameter is required");
return;
}
// Update identity in storage
storage.setSetting("background_task_identity", identity);
// Update JWT manager
if (jwtManager != null) {
jwtManager.updateActiveDid(identity);
}
Log.i(TAG, "Background task identity updated: " + identity);
JSObject result = new JSObject();
result.put("success", true);
result.put("identity", identity);
result.put("message", "Background task identity updated");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error updating background task identity", e);
call.reject("Failed to update background task identity: " + e.getMessage());
}
}
/**
* Test JWT generation
*
* @param call Plugin call
*/
public void testJWTGeneration(PluginCall call) {
try {
Log.d(TAG, "Testing JWT generation");
if (jwtManager == null) {
call.reject("JWT manager not initialized");
return;
}
// Generate test JWT
String jwt = jwtManager.generateJWT();
if (jwt != null && !jwt.isEmpty()) {
JSObject result = new JSObject();
result.put("success", true);
result.put("jwt", jwt);
result.put("message", "JWT generated successfully");
call.resolve(result);
} else {
call.reject("Failed to generate JWT");
}
} catch (Exception e) {
Log.e(TAG, "Error testing JWT generation", e);
call.reject("Failed to test JWT generation: " + e.getMessage());
}
}
/**
* Test Endorser API
*
* @param call Plugin call
*/
public void testEndorserAPI(PluginCall call) {
try {
Log.d(TAG, "Testing Endorser API");
String endpoint = call.getString("endpoint", "https://api.timesafari.com/endorser");
String method = call.getString("method", "GET");
if (enhancedFetcher == null) {
call.reject("Enhanced fetcher not initialized");
return;
}
// Test API call
boolean success = enhancedFetcher.testEndorserAPI(endpoint, method);
JSObject result = new JSObject();
result.put("success", success);
result.put("endpoint", endpoint);
result.put("method", method);
result.put("message", success ? "Endorser API test successful" : "Endorser API test failed");
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error testing Endorser API", e);
call.reject("Failed to test Endorser API: " + e.getMessage());
}
}
}
Loading…
Cancel
Save