You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

969 lines
34 KiB

/**
* DailyNotificationPlugin.java
*
* Android implementation of the Daily Notification Plugin for Capacitor
* Implements offline-first daily notifications with prefetch → cache → schedule → display pipeline
*
* @author Matthew Raymer
* @version 1.0.0
*/
package com.timesafari.dailynotification;
import android.Manifest;
import android.app.AlarmManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.PowerManager;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.work.WorkManager;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
/**
* Main plugin class for handling daily notifications on Android
*
* This plugin provides functionality for scheduling and managing daily notifications
* with offline-first approach, background content fetching, and reliable delivery.
*/
@CapacitorPlugin(
name = "DailyNotification",
permissions = {
@Permission(
alias = "notifications",
strings = {
Manifest.permission.POST_NOTIFICATIONS,
Manifest.permission.SCHEDULE_EXACT_ALARM,
Manifest.permission.WAKE_LOCK,
Manifest.permission.INTERNET
}
)
}
)
public class DailyNotificationPlugin extends Plugin {
private static final String TAG = "DailyNotificationPlugin";
private static final String CHANNEL_ID = "timesafari.daily";
private static final String CHANNEL_NAME = "Daily Notifications";
private static final String CHANNEL_DESCRIPTION = "Daily notification updates from TimeSafari";
private NotificationManager notificationManager;
private AlarmManager alarmManager;
private WorkManager workManager;
private PowerManager powerManager;
private DailyNotificationStorage storage;
private DailyNotificationScheduler scheduler;
private DailyNotificationFetcher fetcher;
// SQLite database components
private DailyNotificationDatabase database;
private DailyNotificationMigration migration;
private String databasePath;
private boolean useSharedStorage = false;
// Rolling window management
private DailyNotificationRollingWindow rollingWindow;
// Exact alarm management
private DailyNotificationExactAlarmManager exactAlarmManager;
// Reboot recovery management
private DailyNotificationRebootRecoveryManager rebootRecoveryManager;
/**
* Initialize the plugin and create notification channel
*/
@Override
public void load() {
super.load();
try {
// Initialize system services
notificationManager = (NotificationManager) getContext()
.getSystemService(Context.NOTIFICATION_SERVICE);
alarmManager = (AlarmManager) getContext()
.getSystemService(Context.ALARM_SERVICE);
workManager = WorkManager.getInstance(getContext());
powerManager = (PowerManager) getContext()
.getSystemService(Context.POWER_SERVICE);
// Initialize components
storage = new DailyNotificationStorage(getContext());
scheduler = new DailyNotificationScheduler(getContext(), alarmManager);
fetcher = new DailyNotificationFetcher(getContext(), storage);
// Initialize TTL enforcer and connect to scheduler
initializeTTLEnforcer();
// Create notification channel
createNotificationChannel();
// Schedule next maintenance
scheduleMaintenance();
Log.i(TAG, "DailyNotificationPlugin initialized successfully");
} catch (Exception e) {
Log.e(TAG, "Failed to initialize DailyNotificationPlugin", e);
}
}
/**
* Configure the plugin with database and storage options
*
* @param call Plugin call containing configuration parameters
*/
@PluginMethod
public void configure(PluginCall call) {
try {
Log.d(TAG, "Configuring plugin with new options");
// Get configuration options
String dbPath = call.getString("dbPath");
String storageMode = call.getString("storage", "tiered");
Integer ttlSeconds = call.getInt("ttlSeconds");
Integer prefetchLeadMinutes = call.getInt("prefetchLeadMinutes");
Integer maxNotificationsPerDay = call.getInt("maxNotificationsPerDay");
Integer retentionDays = call.getInt("retentionDays");
// Update storage mode
useSharedStorage = "shared".equals(storageMode);
// Set database path
if (dbPath != null && !dbPath.isEmpty()) {
databasePath = dbPath;
Log.d(TAG, "Database path set to: " + databasePath);
} else {
// Use default database path
databasePath = getContext().getDatabasePath("daily_notifications.db").getAbsolutePath();
Log.d(TAG, "Using default database path: " + databasePath);
}
// Initialize SQLite database if using shared storage
if (useSharedStorage) {
initializeSQLiteDatabase();
}
// Store configuration in database or SharedPreferences
storeConfiguration(ttlSeconds, prefetchLeadMinutes, maxNotificationsPerDay, retentionDays);
Log.i(TAG, "Plugin configuration completed successfully");
call.resolve();
} catch (Exception e) {
Log.e(TAG, "Error configuring plugin", e);
call.reject("Configuration failed: " + e.getMessage());
}
}
/**
* Initialize SQLite database with migration
*/
private void initializeSQLiteDatabase() {
try {
Log.d(TAG, "Initializing SQLite database");
// Create database instance
database = new DailyNotificationDatabase(getContext(), databasePath);
// Initialize migration utility
migration = new DailyNotificationMigration(getContext(), database);
// Perform migration if needed
if (migration.migrateToSQLite()) {
Log.i(TAG, "Migration completed successfully");
// Validate migration
if (migration.validateMigration()) {
Log.i(TAG, "Migration validation successful");
Log.i(TAG, migration.getMigrationStats());
} else {
Log.w(TAG, "Migration validation failed");
}
} else {
Log.w(TAG, "Migration failed or not needed");
}
} catch (Exception e) {
Log.e(TAG, "Error initializing SQLite database", e);
throw new RuntimeException("SQLite initialization failed", e);
}
}
/**
* Store configuration values
*/
private void storeConfiguration(Integer ttlSeconds, Integer prefetchLeadMinutes,
Integer maxNotificationsPerDay, Integer retentionDays) {
try {
if (useSharedStorage && database != null) {
// Store in SQLite
storeConfigurationInSQLite(ttlSeconds, prefetchLeadMinutes, maxNotificationsPerDay, retentionDays);
} else {
// Store in SharedPreferences
storeConfigurationInSharedPreferences(ttlSeconds, prefetchLeadMinutes, maxNotificationsPerDay, retentionDays);
}
} catch (Exception e) {
Log.e(TAG, "Error storing configuration", e);
}
}
/**
* Store configuration in SQLite database
*/
private void storeConfigurationInSQLite(Integer ttlSeconds, Integer prefetchLeadMinutes,
Integer maxNotificationsPerDay, Integer retentionDays) {
try {
SQLiteDatabase db = database.getWritableDatabase();
// Store each configuration value
if (ttlSeconds != null) {
storeConfigValue(db, "ttlSeconds", String.valueOf(ttlSeconds));
}
if (prefetchLeadMinutes != null) {
storeConfigValue(db, "prefetchLeadMinutes", String.valueOf(prefetchLeadMinutes));
}
if (maxNotificationsPerDay != null) {
storeConfigValue(db, "maxNotificationsPerDay", String.valueOf(maxNotificationsPerDay));
}
if (retentionDays != null) {
storeConfigValue(db, "retentionDays", String.valueOf(retentionDays));
}
Log.d(TAG, "Configuration stored in SQLite");
} catch (Exception e) {
Log.e(TAG, "Error storing configuration in SQLite", e);
}
}
/**
* Store a single configuration value in SQLite
*/
private void storeConfigValue(SQLiteDatabase db, String key, String value) {
ContentValues values = new ContentValues();
values.put(DailyNotificationDatabase.COL_CONFIG_K, key);
values.put(DailyNotificationDatabase.COL_CONFIG_V, value);
// Use INSERT OR REPLACE to handle updates
db.replace(DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null, values);
}
/**
* Store configuration in SharedPreferences
*/
private void storeConfigurationInSharedPreferences(Integer ttlSeconds, Integer prefetchLeadMinutes,
Integer maxNotificationsPerDay, Integer retentionDays) {
try {
SharedPreferences prefs = getContext().getSharedPreferences("DailyNotificationPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
if (ttlSeconds != null) {
editor.putInt("ttlSeconds", ttlSeconds);
}
if (prefetchLeadMinutes != null) {
editor.putInt("prefetchLeadMinutes", prefetchLeadMinutes);
}
if (maxNotificationsPerDay != null) {
editor.putInt("maxNotificationsPerDay", maxNotificationsPerDay);
}
if (retentionDays != null) {
editor.putInt("retentionDays", retentionDays);
}
editor.apply();
Log.d(TAG, "Configuration stored in SharedPreferences");
} catch (Exception e) {
Log.e(TAG, "Error storing configuration in SharedPreferences", e);
}
}
/**
* Initialize TTL enforcer and connect to scheduler
*/
private void initializeTTLEnforcer() {
try {
Log.d(TAG, "Initializing TTL enforcer");
// Create TTL enforcer with current storage mode
DailyNotificationTTLEnforcer ttlEnforcer = new DailyNotificationTTLEnforcer(
getContext(),
database,
useSharedStorage
);
// Connect to scheduler
scheduler.setTTLEnforcer(ttlEnforcer);
// Initialize rolling window
initializeRollingWindow(ttlEnforcer);
Log.i(TAG, "TTL enforcer initialized and connected to scheduler");
} catch (Exception e) {
Log.e(TAG, "Error initializing TTL enforcer", e);
}
}
/**
* Initialize rolling window manager
*/
private void initializeRollingWindow(DailyNotificationTTLEnforcer ttlEnforcer) {
try {
Log.d(TAG, "Initializing rolling window manager");
// Detect platform (Android vs iOS)
boolean isIOSPlatform = false; // TODO: Implement platform detection
// Create rolling window manager
rollingWindow = new DailyNotificationRollingWindow(
getContext(),
scheduler,
ttlEnforcer,
storage,
isIOSPlatform
);
// Initialize exact alarm manager
initializeExactAlarmManager();
// Initialize reboot recovery manager
initializeRebootRecoveryManager();
// Start initial window maintenance
rollingWindow.maintainRollingWindow();
Log.i(TAG, "Rolling window manager initialized");
} catch (Exception e) {
Log.e(TAG, "Error initializing rolling window manager", e);
}
}
/**
* Initialize exact alarm manager
*/
private void initializeExactAlarmManager() {
try {
Log.d(TAG, "Initializing exact alarm manager");
// Create exact alarm manager
exactAlarmManager = new DailyNotificationExactAlarmManager(
getContext(),
alarmManager,
scheduler
);
// Connect to scheduler
scheduler.setExactAlarmManager(exactAlarmManager);
Log.i(TAG, "Exact alarm manager initialized");
} catch (Exception e) {
Log.e(TAG, "Error initializing exact alarm manager", e);
}
}
/**
* Initialize reboot recovery manager
*/
private void initializeRebootRecoveryManager() {
try {
Log.d(TAG, "Initializing reboot recovery manager");
// Create reboot recovery manager
rebootRecoveryManager = new DailyNotificationRebootRecoveryManager(
getContext(),
scheduler,
exactAlarmManager,
rollingWindow
);
// Register broadcast receivers
rebootRecoveryManager.registerReceivers();
Log.i(TAG, "Reboot recovery manager initialized");
} catch (Exception e) {
Log.e(TAG, "Error initializing reboot recovery manager", e);
}
}
/**
* Schedule a daily notification with the specified options
*
* @param call Plugin call containing notification parameters
*/
@PluginMethod
public void scheduleDailyNotification(PluginCall call) {
try {
Log.d(TAG, "Scheduling daily notification");
// 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 Update");
String body = call.getString("body", "Your daily notification is ready");
boolean sound = call.getBoolean("sound", true);
String priority = call.getString("priority", "default");
String url = call.getString("url", "");
// Create notification content
NotificationContent content = new NotificationContent();
content.setTitle(title);
content.setBody(body);
content.setSound(sound);
content.setPriority(priority);
content.setUrl(url);
content.setScheduledTime(calculateNextScheduledTime(hour, minute));
// Store notification content
storage.saveNotificationContent(content);
// Schedule the notification
boolean scheduled = scheduler.scheduleNotification(content);
if (scheduled) {
// Schedule background fetch for next day
scheduleBackgroundFetch(content.getScheduledTime());
Log.i(TAG, "Daily notification scheduled successfully for " + time);
call.resolve();
} else {
call.reject("Failed to schedule notification");
}
} catch (Exception e) {
Log.e(TAG, "Error scheduling daily notification", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Get the last notification that was delivered
*
* @param call Plugin call
*/
@PluginMethod
public void getLastNotification(PluginCall call) {
try {
Log.d(TAG, "Getting last notification");
NotificationContent lastNotification = storage.getLastNotification();
if (lastNotification != null) {
JSObject result = new JSObject();
result.put("id", lastNotification.getId());
result.put("title", lastNotification.getTitle());
result.put("body", lastNotification.getBody());
result.put("timestamp", lastNotification.getScheduledTime());
result.put("url", lastNotification.getUrl());
call.resolve(result);
} else {
call.resolve(null);
}
} catch (Exception e) {
Log.e(TAG, "Error getting last notification", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Cancel all scheduled notifications
*
* @param call Plugin call
*/
@PluginMethod
public void cancelAllNotifications(PluginCall call) {
try {
Log.d(TAG, "Cancelling all notifications");
scheduler.cancelAllNotifications();
storage.clearAllNotifications();
Log.i(TAG, "All notifications cancelled successfully");
call.resolve();
} catch (Exception e) {
Log.e(TAG, "Error cancelling notifications", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Get the current status of notifications
*
* @param call Plugin call
*/
@PluginMethod
public void getNotificationStatus(PluginCall call) {
try {
Log.d(TAG, "Getting notification status");
JSObject result = new JSObject();
// Check if notifications are enabled
boolean notificationsEnabled = areNotificationsEnabled();
result.put("isEnabled", notificationsEnabled);
// Get next notification time
long nextNotificationTime = scheduler.getNextNotificationTime();
result.put("nextNotificationTime", nextNotificationTime);
// Get current settings
JSObject settings = new JSObject();
settings.put("sound", true);
settings.put("priority", "default");
settings.put("timezone", "UTC");
result.put("settings", settings);
// Get pending notifications count
int pendingCount = scheduler.getPendingNotificationsCount();
result.put("pending", pendingCount);
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error getting notification status", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Update notification settings
*
* @param call Plugin call containing new settings
*/
@PluginMethod
public void updateSettings(PluginCall call) {
try {
Log.d(TAG, "Updating notification settings");
// Extract settings
Boolean sound = call.getBoolean("sound");
String priority = call.getString("priority");
String timezone = call.getString("timezone");
// Update settings in storage
if (sound != null) {
storage.setSoundEnabled(sound);
}
if (priority != null) {
storage.setPriority(priority);
}
if (timezone != null) {
storage.setTimezone(timezone);
}
// Update existing notifications with new settings
scheduler.updateNotificationSettings();
Log.i(TAG, "Notification settings updated successfully");
call.resolve();
} catch (Exception e) {
Log.e(TAG, "Error updating notification settings", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Get battery status information
*
* @param call Plugin call
*/
@PluginMethod
public void getBatteryStatus(PluginCall call) {
try {
Log.d(TAG, "Getting battery status");
JSObject result = new JSObject();
// Get battery level (simplified - would need BatteryManager in real implementation)
result.put("level", 100); // Placeholder
result.put("isCharging", false); // Placeholder
result.put("powerState", 0); // Placeholder
result.put("isOptimizationExempt", false); // Placeholder
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error getting battery status", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Request battery optimization exemption
*
* @param call Plugin call
*/
@PluginMethod
public void requestBatteryOptimizationExemption(PluginCall call) {
try {
Log.d(TAG, "Requesting battery optimization exemption");
// This would typically open system settings
// For now, just log the request
Log.i(TAG, "Battery optimization exemption requested");
call.resolve();
} catch (Exception e) {
Log.e(TAG, "Error requesting battery optimization exemption", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Set adaptive scheduling based on device state
*
* @param call Plugin call containing enabled flag
*/
@PluginMethod
public void setAdaptiveScheduling(PluginCall call) {
try {
Log.d(TAG, "Setting adaptive scheduling");
boolean enabled = call.getBoolean("enabled", true);
storage.setAdaptiveSchedulingEnabled(enabled);
if (enabled) {
scheduler.enableAdaptiveScheduling();
} else {
scheduler.disableAdaptiveScheduling();
}
Log.i(TAG, "Adaptive scheduling " + (enabled ? "enabled" : "disabled"));
call.resolve();
} catch (Exception e) {
Log.e(TAG, "Error setting adaptive scheduling", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Get current power state information
*
* @param call Plugin call
*/
@PluginMethod
public void getPowerState(PluginCall call) {
try {
Log.d(TAG, "Getting power state");
JSObject result = new JSObject();
result.put("powerState", 0); // Placeholder
result.put("isOptimizationExempt", false); // Placeholder
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "Error getting power state", e);
call.reject("Internal error: " + e.getMessage());
}
}
/**
* Create the notification channel for Android 8.0+
*/
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription(CHANNEL_DESCRIPTION);
channel.enableLights(true);
channel.enableVibration(true);
notificationManager.createNotificationChannel(channel);
Log.d(TAG, "Notification channel created: " + CHANNEL_ID);
}
}
/**
* Calculate the next scheduled time for the notification
*
* @param hour Hour of day (0-23)
* @param minute Minute of hour (0-59)
* @return Timestamp in milliseconds
*/
private long calculateNextScheduledTime(int hour, int minute) {
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_YEAR, 1);
}
return calendar.getTimeInMillis();
}
/**
* Schedule background fetch for content
*
* @param scheduledTime When the notification is scheduled for
*/
private void scheduleBackgroundFetch(long scheduledTime) {
try {
// Schedule fetch 1 hour before notification
long fetchTime = scheduledTime - TimeUnit.HOURS.toMillis(1);
if (fetchTime > System.currentTimeMillis()) {
fetcher.scheduleFetch(fetchTime);
Log.d(TAG, "Background fetch scheduled for " + fetchTime);
}
} catch (Exception e) {
Log.e(TAG, "Error scheduling background fetch", e);
}
}
/**
* Schedule maintenance tasks
*/
private void scheduleMaintenance() {
try {
// Schedule daily maintenance at 2 AM
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 2);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_YEAR, 1);
}
// This would typically use WorkManager for maintenance
Log.d(TAG, "Maintenance scheduled for " + calendar.getTimeInMillis());
} catch (Exception e) {
Log.e(TAG, "Error scheduling maintenance", e);
}
}
/**
* Check if notifications are enabled
*
* @return true if notifications are enabled
*/
private boolean areNotificationsEnabled() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return getContext().checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED;
}
return NotificationManagerCompat.from(getContext()).areNotificationsEnabled();
}
/**
* Maintain rolling window (for testing or manual triggers)
*
* @param call Plugin call
*/
@PluginMethod
public void maintainRollingWindow(PluginCall call) {
try {
Log.d(TAG, "Manual rolling window maintenance requested");
if (rollingWindow != null) {
rollingWindow.forceMaintenance();
call.resolve();
} else {
call.reject("Rolling window not initialized");
}
} catch (Exception e) {
Log.e(TAG, "Error during manual rolling window maintenance", e);
call.reject("Error maintaining rolling window: " + e.getMessage());
}
}
/**
* Get rolling window statistics
*
* @param call Plugin call
*/
@PluginMethod
public void getRollingWindowStats(PluginCall call) {
try {
Log.d(TAG, "Rolling window stats requested");
if (rollingWindow != null) {
String stats = rollingWindow.getRollingWindowStats();
JSObject result = new JSObject();
result.put("stats", stats);
result.put("maintenanceNeeded", rollingWindow.isMaintenanceNeeded());
result.put("timeUntilNextMaintenance", rollingWindow.getTimeUntilNextMaintenance());
call.resolve(result);
} else {
call.reject("Rolling window not initialized");
}
} catch (Exception e) {
Log.e(TAG, "Error getting rolling window stats", e);
call.reject("Error getting rolling window stats: " + e.getMessage());
}
}
/**
* Get exact alarm status
*
* @param call Plugin call
*/
@PluginMethod
public void getExactAlarmStatus(PluginCall call) {
try {
Log.d(TAG, "Exact alarm status requested");
if (exactAlarmManager != null) {
DailyNotificationExactAlarmManager.ExactAlarmStatus status = exactAlarmManager.getExactAlarmStatus();
JSObject result = new JSObject();
result.put("supported", status.supported);
result.put("enabled", status.enabled);
result.put("canSchedule", status.canSchedule);
result.put("fallbackWindow", status.fallbackWindow.description);
call.resolve(result);
} else {
call.reject("Exact alarm manager not initialized");
}
} catch (Exception e) {
Log.e(TAG, "Error getting exact alarm status", e);
call.reject("Error getting exact alarm status: " + e.getMessage());
}
}
/**
* Request exact alarm permission
*
* @param call Plugin call
*/
@PluginMethod
public void requestExactAlarmPermission(PluginCall call) {
try {
Log.d(TAG, "Exact alarm permission request");
if (exactAlarmManager != null) {
boolean success = exactAlarmManager.requestExactAlarmPermission();
if (success) {
call.resolve();
} else {
call.reject("Failed to request exact alarm permission");
}
} else {
call.reject("Exact alarm manager not initialized");
}
} catch (Exception e) {
Log.e(TAG, "Error requesting exact alarm permission", e);
call.reject("Error requesting exact alarm permission: " + e.getMessage());
}
}
/**
* Open exact alarm settings
*
* @param call Plugin call
*/
@PluginMethod
public void openExactAlarmSettings(PluginCall call) {
try {
Log.d(TAG, "Opening exact alarm settings");
if (exactAlarmManager != null) {
boolean success = exactAlarmManager.openExactAlarmSettings();
if (success) {
call.resolve();
} else {
call.reject("Failed to open exact alarm settings");
}
} else {
call.reject("Exact alarm manager not initialized");
}
} catch (Exception e) {
Log.e(TAG, "Error opening exact alarm settings", e);
call.reject("Error opening exact alarm settings: " + e.getMessage());
}
}
/**
* Get reboot recovery status
*
* @param call Plugin call
*/
@PluginMethod
public void getRebootRecoveryStatus(PluginCall call) {
try {
Log.d(TAG, "Reboot recovery status requested");
if (rebootRecoveryManager != null) {
DailyNotificationRebootRecoveryManager.RecoveryStatus status = rebootRecoveryManager.getRecoveryStatus();
JSObject result = new JSObject();
result.put("inProgress", status.inProgress);
result.put("lastRecoveryTime", status.lastRecoveryTime);
result.put("timeSinceLastRecovery", status.timeSinceLastRecovery);
result.put("recoveryNeeded", rebootRecoveryManager.isRecoveryNeeded());
call.resolve(result);
} else {
call.reject("Reboot recovery manager not initialized");
}
} catch (Exception e) {
Log.e(TAG, "Error getting reboot recovery status", e);
call.reject("Error getting reboot recovery status: " + e.getMessage());
}
}
}