diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPluginModular.java b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPluginModular.java new file mode 100644 index 0000000..5f5556a --- /dev/null +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPluginModular.java @@ -0,0 +1,411 @@ +/** + * DailyNotificationPlugin.java - Core Plugin Interface + * + * Modular Android implementation of the Daily Notification Plugin for Capacitor + * Delegates functionality to specialized manager classes for better maintainability + * + * @author Matthew Raymer + * @version 2.0.0 - Modular Architecture + */ + +package com.timesafari.dailynotification; + +import android.Manifest; +import android.content.Context; +import android.util.Log; + +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; + +/** + * Core plugin class for handling daily notifications on Android + * + * This modular plugin delegates functionality to specialized manager classes: + * - NotificationManager: Core notification operations + * - PermissionManager: Permission handling and settings + * - PowerManager: Battery and power management + * - RecoveryManager: Recovery and maintenance operations + * - ExactAlarmManager: Exact alarm management + * - TimeSafariIntegrationManager: TimeSafari-specific features + * - TaskCoordinationManager: Background task coordination + * - ReminderManager: Daily reminder management + */ +@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"; + + // Core system services + private Context context; + + // Specialized manager components + private NotificationManager notificationManager; + private PermissionManager permissionManager; + private PowerManager powerManager; + private RecoveryManager recoveryManager; + private ExactAlarmManager exactAlarmManager; + private TimeSafariIntegrationManager timeSafariIntegrationManager; + private TaskCoordinationManager taskCoordinationManager; + private ReminderManager reminderManager; + + // Core storage and scheduling components + private DailyNotificationStorage storage; + private DailyNotificationScheduler scheduler; + private ChannelManager channelManager; + + /** + * Initialize the plugin and all manager components + */ + @Override + public void load() { + super.load(); + Log.i(TAG, "Modular DailyNotificationPlugin loaded"); + + try { + context = getContext(); + + // Initialize core components + initializeCoreComponents(); + + // Initialize specialized managers + initializeManagers(); + + // Perform startup recovery if needed + performStartupRecovery(); + + Log.i(TAG, "Modular DailyNotificationPlugin initialized successfully"); + + } catch (Exception e) { + Log.e(TAG, "Failed to initialize DailyNotificationPlugin", e); + } + } + + /** + * Initialize core storage and scheduling components + */ + private void initializeCoreComponents() { + Log.d(TAG, "Initializing core components..."); + + storage = new DailyNotificationStorage(context); + scheduler = new DailyNotificationScheduler(context, + (android.app.AlarmManager) context.getSystemService(Context.ALARM_SERVICE)); + channelManager = new ChannelManager(context); + + // Ensure notification channel exists + if (!channelManager.ensureChannelExists()) { + Log.w(TAG, "Notification channel is blocked - notifications will not appear"); + channelManager.logChannelStatus(); + } + + Log.d(TAG, "Core components initialized"); + } + + /** + * Initialize all specialized manager components + */ + private void initializeManagers() { + Log.d(TAG, "Initializing specialized managers..."); + + notificationManager = new NotificationManager(context, storage, scheduler, channelManager); + permissionManager = new PermissionManager(context, channelManager); + powerManager = new PowerManager(context); + recoveryManager = new RecoveryManager(context, storage, scheduler); + exactAlarmManager = new ExactAlarmManager(context); + timeSafariIntegrationManager = new TimeSafariIntegrationManager(context, storage); + taskCoordinationManager = new TaskCoordinationManager(context, storage); + reminderManager = new ReminderManager(context, storage, scheduler); + + Log.d(TAG, "Specialized managers initialized"); + } + + /** + * Perform startup recovery if needed + */ + private void performStartupRecovery() { + Log.d(TAG, "Checking if startup recovery is needed..."); + + try { + RecoveryManager recoveryManager = RecoveryManager.getInstance(context, storage, scheduler); + boolean recoveryPerformed = recoveryManager.performRecoveryIfNeeded("APP_STARTUP"); + + if (recoveryPerformed) { + Log.i(TAG, "Startup recovery completed successfully"); + } else { + Log.d(TAG, "Startup recovery skipped (not needed or already performed)"); + } + } catch (Exception e) { + Log.e(TAG, "Error during startup recovery", e); + } + } + + // ============================================================================ + // CORE PLUGIN METHODS - Delegation to specialized managers + // ============================================================================ + + /** + * Configure the plugin with database and storage options + * Delegates to NotificationManager for configuration handling + */ + @PluginMethod + public void configure(PluginCall call) { + Log.d(TAG, "Delegating configure to NotificationManager"); + notificationManager.configure(call); + } + + /** + * Get comprehensive status of the notification system + * Delegates to PermissionManager for status checking + */ + @PluginMethod + public void checkStatus(PluginCall call) { + Log.d(TAG, "Delegating checkStatus to PermissionManager"); + permissionManager.checkStatus(call); + } + + // ============================================================================ + // NOTIFICATION MANAGEMENT METHODS - Delegation to NotificationManager + // ============================================================================ + + @PluginMethod + public void scheduleDailyNotification(PluginCall call) { + Log.d(TAG, "Delegating scheduleDailyNotification to NotificationManager"); + notificationManager.scheduleDailyNotification(call); + } + + @PluginMethod + public void getLastNotification(PluginCall call) { + Log.d(TAG, "Delegating getLastNotification to NotificationManager"); + notificationManager.getLastNotification(call); + } + + @PluginMethod + public void cancelAllNotifications(PluginCall call) { + Log.d(TAG, "Delegating cancelAllNotifications to NotificationManager"); + notificationManager.cancelAllNotifications(call); + } + + @PluginMethod + public void getNotificationStatus(PluginCall call) { + Log.d(TAG, "Delegating getNotificationStatus to NotificationManager"); + notificationManager.getNotificationStatus(call); + } + + @PluginMethod + public void updateSettings(PluginCall call) { + Log.d(TAG, "Delegating updateSettings to NotificationManager"); + notificationManager.updateSettings(call); + } + + // ============================================================================ + // PERMISSION MANAGEMENT METHODS - Delegation to PermissionManager + // ============================================================================ + + @PluginMethod + public void requestNotificationPermissions(PluginCall call) { + Log.d(TAG, "Delegating requestNotificationPermissions to PermissionManager"); + permissionManager.requestNotificationPermissions(call); + } + + @PluginMethod + public void checkPermissionStatus(PluginCall call) { + Log.d(TAG, "Delegating checkPermissionStatus to PermissionManager"); + permissionManager.checkPermissionStatus(call); + } + + @PluginMethod + public void openExactAlarmSettings(PluginCall call) { + Log.d(TAG, "Delegating openExactAlarmSettings to PermissionManager"); + permissionManager.openExactAlarmSettings(call); + } + + @PluginMethod + public void isChannelEnabled(PluginCall call) { + Log.d(TAG, "Delegating isChannelEnabled to PermissionManager"); + permissionManager.isChannelEnabled(call); + } + + @PluginMethod + public void openChannelSettings(PluginCall call) { + Log.d(TAG, "Delegating openChannelSettings to PermissionManager"); + permissionManager.openChannelSettings(call); + } + + // ============================================================================ + // POWER MANAGEMENT METHODS - Delegation to PowerManager + // ============================================================================ + + @PluginMethod + public void getBatteryStatus(PluginCall call) { + Log.d(TAG, "Delegating getBatteryStatus to PowerManager"); + powerManager.getBatteryStatus(call); + } + + @PluginMethod + public void requestBatteryOptimizationExemption(PluginCall call) { + Log.d(TAG, "Delegating requestBatteryOptimizationExemption to PowerManager"); + powerManager.requestBatteryOptimizationExemption(call); + } + + @PluginMethod + public void setAdaptiveScheduling(PluginCall call) { + Log.d(TAG, "Delegating setAdaptiveScheduling to PowerManager"); + powerManager.setAdaptiveScheduling(call); + } + + @PluginMethod + public void getPowerState(PluginCall call) { + Log.d(TAG, "Delegating getPowerState to PowerManager"); + powerManager.getPowerState(call); + } + + // ============================================================================ + // RECOVERY MANAGEMENT METHODS - Delegation to RecoveryManager + // ============================================================================ + + @PluginMethod + public void getRecoveryStats(PluginCall call) { + Log.d(TAG, "Delegating getRecoveryStats to RecoveryManager"); + recoveryManager.getRecoveryStats(call); + } + + @PluginMethod + public void maintainRollingWindow(PluginCall call) { + Log.d(TAG, "Delegating maintainRollingWindow to RecoveryManager"); + recoveryManager.maintainRollingWindow(call); + } + + @PluginMethod + public void getRollingWindowStats(PluginCall call) { + Log.d(TAG, "Delegating getRollingWindowStats to RecoveryManager"); + recoveryManager.getRollingWindowStats(call); + } + + @PluginMethod + public void getRebootRecoveryStatus(PluginCall call) { + Log.d(TAG, "Delegating getRebootRecoveryStatus to RecoveryManager"); + recoveryManager.getRebootRecoveryStatus(call); + } + + // ============================================================================ + // EXACT ALARM MANAGEMENT METHODS - Delegation to ExactAlarmManager + // ============================================================================ + + @PluginMethod + public void getExactAlarmStatus(PluginCall call) { + Log.d(TAG, "Delegating getExactAlarmStatus to ExactAlarmManager"); + exactAlarmManager.getExactAlarmStatus(call); + } + + @PluginMethod + public void requestExactAlarmPermission(PluginCall call) { + Log.d(TAG, "Delegating requestExactAlarmPermission to ExactAlarmManager"); + exactAlarmManager.requestExactAlarmPermission(call); + } + + // ============================================================================ + // TIMESAFARI INTEGRATION METHODS - Delegation to TimeSafariIntegrationManager + // ============================================================================ + + @PluginMethod + public void setActiveDidFromHost(PluginCall call) { + Log.d(TAG, "Delegating setActiveDidFromHost to TimeSafariIntegrationManager"); + timeSafariIntegrationManager.setActiveDidFromHost(call); + } + + @PluginMethod + public void refreshAuthenticationForNewIdentity(PluginCall call) { + Log.d(TAG, "Delegating refreshAuthenticationForNewIdentity to TimeSafariIntegrationManager"); + timeSafariIntegrationManager.refreshAuthenticationForNewIdentity(call); + } + + @PluginMethod + public void clearCacheForNewIdentity(PluginCall call) { + Log.d(TAG, "Delegating clearCacheForNewIdentity to TimeSafariIntegrationManager"); + timeSafariIntegrationManager.clearCacheForNewIdentity(call); + } + + @PluginMethod + public void updateBackgroundTaskIdentity(PluginCall call) { + Log.d(TAG, "Delegating updateBackgroundTaskIdentity to TimeSafariIntegrationManager"); + timeSafariIntegrationManager.updateBackgroundTaskIdentity(call); + } + + @PluginMethod + public void testJWTGeneration(PluginCall call) { + Log.d(TAG, "Delegating testJWTGeneration to TimeSafariIntegrationManager"); + timeSafariIntegrationManager.testJWTGeneration(call); + } + + @PluginMethod + public void testEndorserAPI(PluginCall call) { + Log.d(TAG, "Delegating testEndorserAPI to TimeSafariIntegrationManager"); + timeSafariIntegrationManager.testEndorserAPI(call); + } + + // ============================================================================ + // TASK COORDINATION METHODS - Delegation to TaskCoordinationManager + // ============================================================================ + + @PluginMethod + public void coordinateBackgroundTasks(PluginCall call) { + Log.d(TAG, "Delegating coordinateBackgroundTasks to TaskCoordinationManager"); + taskCoordinationManager.coordinateBackgroundTasks(call); + } + + @PluginMethod + public void handleAppLifecycleEvent(PluginCall call) { + Log.d(TAG, "Delegating handleAppLifecycleEvent to TaskCoordinationManager"); + taskCoordinationManager.handleAppLifecycleEvent(call); + } + + @PluginMethod + public void getCoordinationStatus(PluginCall call) { + Log.d(TAG, "Delegating getCoordinationStatus to TaskCoordinationManager"); + taskCoordinationManager.getCoordinationStatus(call); + } + + // ============================================================================ + // REMINDER MANAGEMENT METHODS - Delegation to ReminderManager + // ============================================================================ + + @PluginMethod + public void scheduleDailyReminder(PluginCall call) { + Log.d(TAG, "Delegating scheduleDailyReminder to ReminderManager"); + reminderManager.scheduleDailyReminder(call); + } + + @PluginMethod + public void cancelDailyReminder(PluginCall call) { + Log.d(TAG, "Delegating cancelDailyReminder to ReminderManager"); + reminderManager.cancelDailyReminder(call); + } + + @PluginMethod + public void getScheduledReminders(PluginCall call) { + Log.d(TAG, "Delegating getScheduledReminders to ReminderManager"); + reminderManager.getScheduledReminders(call); + } + + @PluginMethod + public void updateDailyReminder(PluginCall call) { + Log.d(TAG, "Delegating updateDailyReminder to ReminderManager"); + reminderManager.updateDailyReminder(call); + } +} diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/NotificationManager.java b/android/plugin/src/main/java/com/timesafari/dailynotification/NotificationManager.java new file mode 100644 index 0000000..0c928e8 --- /dev/null +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/NotificationManager.java @@ -0,0 +1,363 @@ +/** + * NotificationManager.java + * + * Specialized manager for core notification operations + * Handles scheduling, cancellation, status checking, and settings management + * + * @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.Calendar; + +/** + * Manager class for core notification operations + * + * Responsibilities: + * - Schedule daily notifications + * - Cancel notifications + * - Get notification status and history + * - Update notification settings + * - Handle configuration + */ +public class NotificationManager { + + private static final String TAG = "NotificationManager"; + + private final Context context; + private final DailyNotificationStorage storage; + private final DailyNotificationScheduler scheduler; + private final ChannelManager channelManager; + + // Configuration state + private String databasePath; + private boolean useSharedStorage = false; + + /** + * Initialize the NotificationManager + * + * @param context Android context + * @param storage Storage component for notification data + * @param scheduler Scheduler component for alarm management + * @param channelManager Channel manager for notification channels + */ + public NotificationManager(Context context, DailyNotificationStorage storage, + DailyNotificationScheduler scheduler, ChannelManager channelManager) { + this.context = context; + this.storage = storage; + this.scheduler = scheduler; + this.channelManager = channelManager; + + Log.d(TAG, "NotificationManager initialized"); + } + + /** + * Configure the plugin with database and storage options + * + * @param call Plugin call containing configuration parameters + */ + public void configure(PluginCall call) { + try { + Log.d(TAG, "Configuring notification system"); + + // 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 { + databasePath = context.getDatabasePath("daily_notifications.db").getAbsolutePath(); + Log.d(TAG, "Using default database path: " + databasePath); + } + + // Store configuration + storeConfiguration(ttlSeconds, prefetchLeadMinutes, maxNotificationsPerDay, retentionDays); + + Log.i(TAG, "Notification system configuration completed"); + + JSObject result = new JSObject(); + result.put("success", true); + result.put("message", "Configuration updated successfully"); + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error configuring notification system", e); + call.reject("Configuration failed: " + e.getMessage()); + } + } + + /** + * Schedule a daily notification with the specified options + * + * @param call Plugin call containing notification parameters + */ + 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.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 + String notificationId = "daily-" + System.currentTimeMillis(); + content.setId(notificationId); + + // Save notification content + storage.saveNotificationContent(content); + + // Schedule the alarm + boolean scheduled = scheduler.scheduleNotification(content); + + if (scheduled) { + Log.i(TAG, "Daily notification scheduled successfully: " + notificationId); + + JSObject result = new JSObject(); + result.put("success", true); + result.put("notificationId", notificationId); + result.put("scheduledTime", calendar.getTimeInMillis()); + result.put("message", "Notification scheduled successfully"); + call.resolve(result); + } else { + Log.e(TAG, "Failed to schedule daily notification"); + call.reject("Failed to schedule notification"); + } + + } catch (Exception e) { + Log.e(TAG, "Error scheduling daily notification", e); + call.reject("Scheduling failed: " + e.getMessage()); + } + } + + /** + * Get the last notification that was displayed + * + * @param call Plugin call + */ + 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("success", true); + result.put("notification", lastNotification.toJSObject()); + call.resolve(result); + } else { + JSObject result = new JSObject(); + result.put("success", true); + result.put("notification", null); + result.put("message", "No notifications found"); + call.resolve(result); + } + + } catch (Exception e) { + Log.e(TAG, "Error getting last notification", e); + call.reject("Failed to get last notification: " + e.getMessage()); + } + } + + /** + * Cancel all scheduled notifications + * + * @param call Plugin call + */ + public void cancelAllNotifications(PluginCall call) { + try { + Log.d(TAG, "Cancelling all notifications"); + + // Cancel all scheduled alarms + scheduler.cancelAllNotifications(); + + // Clear stored notifications + storage.clearAllNotifications(); + + Log.i(TAG, "All notifications cancelled successfully"); + + JSObject result = new JSObject(); + result.put("success", true); + result.put("message", "All notifications cancelled"); + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error cancelling notifications", e); + call.reject("Failed to cancel notifications: " + e.getMessage()); + } + } + + /** + * Get the current status of the notification system + * + * @param call Plugin call + */ + public void getNotificationStatus(PluginCall call) { + try { + Log.d(TAG, "Getting notification status"); + + // Get scheduled notifications count + int scheduledCount = storage.getScheduledNotificationsCount(); + + // Get last notification + NotificationContent lastNotification = storage.getLastNotification(); + + JSObject result = new JSObject(); + result.put("success", true); + result.put("scheduledCount", scheduledCount); + result.put("lastNotification", lastNotification != null ? lastNotification.toJSObject() : null); + result.put("channelEnabled", channelManager.isChannelEnabled()); + result.put("channelId", channelManager.getDefaultChannelId()); + + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error getting notification status", e); + call.reject("Failed to get notification status: " + e.getMessage()); + } + } + + /** + * Update notification settings + * + * @param call Plugin call containing settings + */ + public void updateSettings(PluginCall call) { + try { + Log.d(TAG, "Updating notification settings"); + + // Get settings from call + String title = call.getString("title"); + String body = call.getString("body"); + Boolean sound = call.getBoolean("sound"); + String priority = call.getString("priority"); + + // Update settings in storage + if (title != null) { + storage.setSetting("default_title", title); + } + if (body != null) { + storage.setSetting("default_body", body); + } + if (sound != null) { + storage.setSetting("default_sound", sound.toString()); + } + if (priority != null) { + storage.setSetting("default_priority", priority); + } + + Log.i(TAG, "Notification settings updated successfully"); + + JSObject result = new JSObject(); + result.put("success", true); + result.put("message", "Settings updated successfully"); + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error updating settings", e); + call.reject("Failed to update settings: " + e.getMessage()); + } + } + + /** + * Store configuration parameters + * + * @param ttlSeconds TTL in seconds + * @param prefetchLeadMinutes Prefetch lead time in minutes + * @param maxNotificationsPerDay Maximum notifications per day + * @param retentionDays Retention period in days + */ + private void storeConfiguration(Integer ttlSeconds, Integer prefetchLeadMinutes, + Integer maxNotificationsPerDay, Integer retentionDays) { + try { + if (ttlSeconds != null) { + storage.setSetting("ttl_seconds", ttlSeconds.toString()); + } + if (prefetchLeadMinutes != null) { + storage.setSetting("prefetch_lead_minutes", prefetchLeadMinutes.toString()); + } + if (maxNotificationsPerDay != null) { + storage.setSetting("max_notifications_per_day", maxNotificationsPerDay.toString()); + } + if (retentionDays != null) { + storage.setSetting("retention_days", retentionDays.toString()); + } + + Log.d(TAG, "Configuration stored successfully"); + } catch (Exception e) { + Log.e(TAG, "Error storing configuration", e); + } + } +} diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/PermissionManager.java b/android/plugin/src/main/java/com/timesafari/dailynotification/PermissionManager.java new file mode 100644 index 0000000..a920725 --- /dev/null +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/PermissionManager.java @@ -0,0 +1,291 @@ +/** + * PermissionManager.java + * + * Specialized manager for permission handling and notification settings + * Handles notification permissions, channel management, and exact alarm settings + * + * @author Matthew Raymer + * @version 2.0.0 - Modular Architecture + */ + +package com.timesafari.dailynotification; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.provider.Settings; +import android.util.Log; + +import androidx.core.app.NotificationManagerCompat; + +import com.getcapacitor.JSObject; +import com.getcapacitor.PluginCall; + +/** + * Manager class for permission and settings management + * + * Responsibilities: + * - Request notification permissions + * - Check permission status + * - Manage notification channels + * - Handle exact alarm settings + * - Provide comprehensive status checking + */ +public class PermissionManager { + + private static final String TAG = "PermissionManager"; + + private final Context context; + private final ChannelManager channelManager; + + /** + * Initialize the PermissionManager + * + * @param context Android context + * @param channelManager Channel manager for notification channels + */ + public PermissionManager(Context context, ChannelManager channelManager) { + this.context = context; + this.channelManager = channelManager; + + Log.d(TAG, "PermissionManager initialized"); + } + + /** + * Request notification permissions from the user + * + * @param call Plugin call + */ + public void requestNotificationPermissions(PluginCall call) { + try { + Log.d(TAG, "Requesting notification permissions"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // For Android 13+, request POST_NOTIFICATIONS permission + requestPermission(Manifest.permission.POST_NOTIFICATIONS, call); + } else { + // For older versions, permissions are granted at install time + JSObject result = new JSObject(); + result.put("success", true); + result.put("granted", true); + result.put("message", "Notifications enabled (pre-Android 13)"); + call.resolve(result); + } + + } catch (Exception e) { + Log.e(TAG, "Error requesting notification permissions", e); + call.reject("Failed to request permissions: " + e.getMessage()); + } + } + + /** + * Check the current status of notification permissions + * + * @param call Plugin call + */ + public void checkPermissionStatus(PluginCall call) { + try { + Log.d(TAG, "Checking permission status"); + + boolean postNotificationsGranted = false; + boolean exactAlarmsGranted = false; + + // Check POST_NOTIFICATIONS permission + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + postNotificationsGranted = context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) + == PackageManager.PERMISSION_GRANTED; + } else { + postNotificationsGranted = NotificationManagerCompat.from(context).areNotificationsEnabled(); + } + + // Check exact alarm permission + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + android.app.AlarmManager alarmManager = (android.app.AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + exactAlarmsGranted = alarmManager.canScheduleExactAlarms(); + } else { + exactAlarmsGranted = true; // Pre-Android 12, exact alarms are always allowed + } + + JSObject result = new JSObject(); + result.put("success", true); + result.put("postNotificationsGranted", postNotificationsGranted); + result.put("exactAlarmsGranted", exactAlarmsGranted); + result.put("channelEnabled", channelManager.isChannelEnabled()); + result.put("channelImportance", channelManager.getChannelImportance()); + + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error checking permission status", e); + call.reject("Failed to check permissions: " + e.getMessage()); + } + } + + /** + * Open exact alarm settings for the user + * + * @param call Plugin call + */ + public void openExactAlarmSettings(PluginCall call) { + try { + Log.d(TAG, "Opening exact alarm settings"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); + intent.setData(android.net.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("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("message", "Exact alarms not supported on this Android version"); + call.resolve(result); + } + + } catch (Exception e) { + Log.e(TAG, "Error opening exact alarm settings", e); + call.reject("Failed to open exact alarm settings: " + e.getMessage()); + } + } + + /** + * Check if the notification channel is enabled + * + * @param call Plugin call + */ + public void isChannelEnabled(PluginCall call) { + try { + Log.d(TAG, "Checking channel status"); + + boolean enabled = channelManager.isChannelEnabled(); + int importance = channelManager.getChannelImportance(); + + JSObject result = new JSObject(); + result.put("success", true); + result.put("enabled", enabled); + result.put("importance", importance); + result.put("channelId", channelManager.getDefaultChannelId()); + + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error checking channel status", e); + call.reject("Failed to check channel status: " + e.getMessage()); + } + } + + /** + * Open notification channel settings for the user + * + * @param call Plugin call + */ + public void openChannelSettings(PluginCall call) { + try { + Log.d(TAG, "Opening channel settings"); + + boolean opened = channelManager.openChannelSettings(); + + JSObject result = new JSObject(); + result.put("success", true); + result.put("opened", opened); + result.put("message", opened ? "Channel settings opened" : "Failed to open channel settings"); + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error opening channel settings", e); + call.reject("Failed to open channel settings: " + e.getMessage()); + } + } + + /** + * Get comprehensive status of the notification system + * + * @param call Plugin call + */ + public void checkStatus(PluginCall call) { + try { + Log.d(TAG, "Checking comprehensive status"); + + // Check permissions + boolean postNotificationsGranted = false; + boolean exactAlarmsGranted = false; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + postNotificationsGranted = context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) + == PackageManager.PERMISSION_GRANTED; + } else { + postNotificationsGranted = NotificationManagerCompat.from(context).areNotificationsEnabled(); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + android.app.AlarmManager alarmManager = (android.app.AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + exactAlarmsGranted = alarmManager.canScheduleExactAlarms(); + } else { + exactAlarmsGranted = true; + } + + // Check channel status + boolean channelEnabled = channelManager.isChannelEnabled(); + int channelImportance = channelManager.getChannelImportance(); + + // Determine overall status + boolean canScheduleNow = postNotificationsGranted && channelEnabled && exactAlarmsGranted; + + JSObject result = new JSObject(); + result.put("success", true); + result.put("canScheduleNow", canScheduleNow); + result.put("postNotificationsGranted", postNotificationsGranted); + result.put("exactAlarmsGranted", exactAlarmsGranted); + result.put("channelEnabled", channelEnabled); + result.put("channelImportance", channelImportance); + result.put("channelId", channelManager.getDefaultChannelId()); + result.put("androidVersion", Build.VERSION.SDK_INT); + + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error checking comprehensive status", e); + call.reject("Failed to check status: " + e.getMessage()); + } + } + + /** + * Request a specific permission + * + * @param permission Permission to request + * @param call Plugin call + */ + private void requestPermission(String permission, PluginCall call) { + try { + // This would typically be handled by the Capacitor framework + // For now, we'll check if the permission is already granted + boolean granted = context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; + + JSObject result = new JSObject(); + result.put("success", true); + result.put("granted", granted); + result.put("permission", permission); + result.put("message", granted ? "Permission already granted" : "Permission not granted"); + call.resolve(result); + + } catch (Exception e) { + Log.e(TAG, "Error requesting permission: " + permission, e); + call.reject("Failed to request permission: " + e.getMessage()); + } + } +}