refactor: begin P1 modularization - create core plugin and managers
- 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.)
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user