Browse Source
- Add DailyNotificationPluginModular.java: Core plugin with delegation pattern - Add NotificationManager.java: Handles core notification operations - Add PermissionManager.java: Handles permissions and channel management - Modular architecture reduces 2,264-line plugin into focused components - Maintains all existing functionality through delegation - Foundation for P1 Priority 1: Split plugin into modules Next: Complete remaining manager classes (PowerManager, RecoveryManager, etc.)master
3 changed files with 1065 additions and 0 deletions
@ -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); |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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()); |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue