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

21 KiB

DailyNotification Plugin - Key Code Snippets for ChatGPT

Created: 2025-10-14 06:44:58 UTC
Author: Matthew Raymer

🔧 Core Plugin Methods

DailyNotificationPlugin.java - Main Methods

@PluginMethod
public void scheduleDailyNotification(PluginCall call) {
    try {
        Log.d(TAG, "Scheduling daily notification");
        ensureStorageInitialized();
        
        String time = call.getString("time", "09:00");
        String title = call.getString("title", "Daily Notification");
        String body = call.getString("body", "Your daily notification");
        boolean sound = call.getBoolean("sound", true);
        String priority = call.getString("priority", "default");
        String url = call.getString("url", "");
        
        // Parse time and schedule notification
        String[] timeParts = time.split(":");
        int hour = Integer.parseInt(timeParts[0]);
        int minute = Integer.parseInt(timeParts[1]);
        
        // Create notification content
        NotificationContent content = new NotificationContent(
            UUID.randomUUID().toString(),
            title,
            body,
            System.currentTimeMillis(),
            sound,
            priority,
            url
        );
        
        // Save to storage
        storage.saveNotificationContent(content);
        
        // Schedule with AlarmManager
        DailyNotificationScheduler scheduler = new DailyNotificationScheduler(getContext());
        scheduler.scheduleNotification(content, hour, minute);
        
        JSObject result = new JSObject();
        result.put("success", true);
        result.put("message", "Notification scheduled for " + time);
        call.resolve(result);
        
    } catch (Exception e) {
        Log.e(TAG, "Error scheduling notification", e);
        call.reject("Error scheduling notification: " + e.getMessage());
    }
}

private void ensureStorageInitialized() throws Exception {
    if (storage == null) {
        Log.w(TAG, "Storage not initialized, initializing now");
        storage = new DailyNotificationStorage(getContext());
        if (storage == null) {
            throw new Exception("Failed to initialize storage");
        }
    }
}

@PluginMethod
public void checkAndPerformRecovery(PluginCall call) {
    try {
        Log.d(TAG, "Checking for recovery needs");
        ensureStorageInitialized();
        
        // Load all saved notifications
        List<NotificationContent> savedNotifications = storage.loadAllNotifications();
        Log.d(TAG, "Found " + savedNotifications.size() + " saved notifications");
        
        if (savedNotifications.isEmpty()) {
            Log.d(TAG, "No notifications to recover");
            call.resolve();
            return;
        }
        
        // Check which notifications need rescheduling
        DailyNotificationScheduler scheduler = new DailyNotificationScheduler(getContext());
        int recoveredCount = 0;
        
        for (NotificationContent notification : savedNotifications) {
            try {
                // Check if alarm is already scheduled
                if (!scheduler.isNotificationScheduled(notification.getId())) {
                    // Reschedule the notification
                    scheduler.scheduleNotification(notification);
                    recoveredCount++;
                    Log.d(TAG, "Recovered notification: " + notification.getId());
                }
            } catch (Exception e) {
                Log.w(TAG, "Failed to recover notification: " + notification.getId(), e);
            }
        }
        
        Log.i(TAG, "Recovery completed: " + recoveredCount + "/" + savedNotifications.size() + " recovered");
        
        JSObject result = new JSObject();
        result.put("recovered", recoveredCount);
        result.put("total", savedNotifications.size());
        call.resolve(result);
        
    } catch (Exception e) {
        Log.e(TAG, "Error during recovery", e);
        call.reject("Error during recovery: " + e.getMessage());
    }
}

🔄 Boot Recovery System

BootReceiver.java - Core Implementation

public class BootReceiver extends BroadcastReceiver {
    private static final String TAG = "BootReceiver";
    private static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
    private static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
    private static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null || intent.getAction() == null) {
            Log.w(TAG, "Received null intent or action");
            return;
        }

        String action = intent.getAction();
        Log.d(TAG, "Received broadcast: " + action);

        try {
            switch (action) {
                case ACTION_LOCKED_BOOT_COMPLETED:
                    handleLockedBootCompleted(context);
                    break;
                case ACTION_BOOT_COMPLETED:
                    handleBootCompleted(context);
                    break;
                case ACTION_MY_PACKAGE_REPLACED:
                    handlePackageReplaced(context, intent);
                    break;
                default:
                    Log.w(TAG, "Unknown action: " + action);
                    break;
            }
        } catch (Exception e) {
            Log.e(TAG, "Error handling broadcast: " + action, e);
        }
    }

    private void handleLockedBootCompleted(Context context) {
        Log.i(TAG, "Locked boot completed - preparing for recovery");
        try {
            Context deviceProtectedContext = context;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                deviceProtectedContext = context.createDeviceProtectedStorageContext();
            }
            Log.i(TAG, "Locked boot completed - ready for full recovery on unlock");
        } catch (Exception e) {
            Log.e(TAG, "Error during locked boot completion", e);
        }
    }

    private void handleBootCompleted(Context context) {
        Log.i(TAG, "Device boot completed - restoring notifications");
        try {
            // Load all saved notifications
            DailyNotificationStorage storage = new DailyNotificationStorage(context);
            List<NotificationContent> notifications = storage.loadAllNotifications();
            
            Log.d(TAG, "Found " + notifications.size() + " notifications to recover");
            
            if (notifications.isEmpty()) {
                Log.d(TAG, "No notifications to recover");
                return;
            }
            
            // Reschedule all notifications
            DailyNotificationScheduler scheduler = new DailyNotificationScheduler(context);
            int recoveredCount = 0;
            
            for (NotificationContent notification : notifications) {
                try {
                    scheduler.scheduleNotification(notification);
                    recoveredCount++;
                    Log.d(TAG, "Recovered notification: " + notification.getId());
                } catch (Exception e) {
                    Log.w(TAG, "Failed to recover notification: " + notification.getId(), e);
                }
            }
            
            Log.i(TAG, "Notification recovery completed: " + recoveredCount + "/" + notifications.size() + " recovered");
            
        } catch (Exception e) {
            Log.e(TAG, "Error during boot recovery", e);
        }
    }

    private void handlePackageReplaced(Context context, Intent intent) {
        Log.i(TAG, "Package replaced - restoring notifications");
        // Use the same logic as boot completed
        handleBootCompleted(context);
    }
}

📊 Data Model

NotificationContent.java - Core Data Structure

@Entity(tableName = "notifications")
public class NotificationContent {
    @PrimaryKey
    private String id;
    
    private String title;
    private String body;
    private long fetchedAt;        // Immutable fetch timestamp
    private long scheduledAt;       // Mutable schedule timestamp
    private String mediaUrl;
    private boolean sound;
    private String priority;
    private String url;
    
    // Transient field for Gson compatibility
    @Expose(serialize = false, deserialize = false)
    private transient long fetchTime;
    
    public NotificationContent(String id, String title, String body, long fetchedAt, 
                             boolean sound, String priority, String url) {
        this.id = id;
        this.title = title;
        this.body = body;
        this.fetchedAt = fetchedAt;
        this.scheduledAt = fetchedAt; // Initialize with fetch time
        this.sound = sound;
        this.priority = priority;
        this.url = url;
        this.fetchTime = fetchedAt; // Set transient field
    }
    
    // Custom JsonDeserializer for Gson
    public static class NotificationContentDeserializer implements JsonDeserializer<NotificationContent> {
        @Override
        public NotificationContent deserialize(JsonElement json, Type typeOfT, 
                                             JsonDeserializationContext context) throws JsonParseException {
            JsonObject jsonObject = json.getAsJsonObject();
            
            String id = jsonObject.get("id").getAsString();
            String title = jsonObject.get("title").getAsString();
            String body = jsonObject.get("body").getAsString();
            long fetchedAt = jsonObject.get("fetchedAt").getAsLong();
            boolean sound = jsonObject.get("sound").getAsBoolean();
            String priority = jsonObject.get("priority").getAsString();
            String url = jsonObject.get("url").getAsString();
            
            // Create with constructor to ensure fetchedAt is set
            NotificationContent content = new NotificationContent(id, title, body, fetchedAt, sound, priority, url);
            
            // Set other fields if present
            if (jsonObject.has("scheduledAt")) {
                content.setScheduledAt(jsonObject.get("scheduledAt").getAsLong());
            }
            if (jsonObject.has("mediaUrl")) {
                content.setMediaUrl(jsonObject.get("mediaUrl").getAsString());
            }
            
            return content;
        }
    }
    
    // Getters and setters...
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    
    public String getBody() { return body; }
    public void setBody(String body) { this.body = body; }
    
    public long getFetchedAt() { return fetchedAt; }
    public void setFetchedAt(long fetchedAt) { this.fetchedAt = fetchedAt; }
    
    public long getScheduledAt() { return scheduledAt; }
    public void setScheduledAt(long scheduledAt) { this.scheduledAt = scheduledAt; }
    
    public String getMediaUrl() { return mediaUrl; }
    public void setMediaUrl(String mediaUrl) { this.mediaUrl = mediaUrl; }
    
    public boolean isSound() { return sound; }
    public void setSound(boolean sound) { this.sound = sound; }
    
    public String getPriority() { return priority; }
    public void setPriority(String priority) { this.priority = priority; }
    
    public String getUrl() { return url; }
    public void setUrl(String url) { this.url = url; }
    
    public long getFetchTime() { return fetchTime; }
    public void setFetchTime(long fetchTime) { this.fetchTime = fetchTime; }
}

🗄️ Storage Implementation

DailyNotificationStorage.java - Key Methods

@Database(entities = {NotificationContent.class}, version = 1)
public abstract class DailyNotificationStorage extends RoomDatabase {
    public abstract NotificationContentDao notificationContentDao();
    
    private static DailyNotificationStorage INSTANCE;
    private static final String DATABASE_NAME = "daily_notifications";
    
    public static DailyNotificationStorage getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (DailyNotificationStorage.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            DailyNotificationStorage.class, DATABASE_NAME)
                            .build();
                }
            }
        }
        return INSTANCE;
    }
    
    public void saveNotificationContent(NotificationContent content) {
        try {
            notificationContentDao().insert(content);
            Log.d(TAG, "Saved notification: " + content.getId());
        } catch (Exception e) {
            Log.e(TAG, "Error saving notification", e);
            throw e;
        }
    }
    
    public List<NotificationContent> loadAllNotifications() {
        try {
            List<NotificationContent> notifications = notificationContentDao().getAllNotifications();
            Log.d(TAG, "Loaded " + notifications.size() + " notifications");
            return notifications;
        } catch (Exception e) {
            Log.e(TAG, "Error loading notifications", e);
            return new ArrayList<>();
        }
    }
    
    public void deleteNotification(String id) {
        try {
            notificationContentDao().deleteById(id);
            Log.d(TAG, "Deleted notification: " + id);
        } catch (Exception e) {
            Log.e(TAG, "Error deleting notification", e);
        }
    }
}

🔔 Notification Scheduling

DailyNotificationScheduler.java - Core Scheduling

public class DailyNotificationScheduler {
    private static final String TAG = "DailyNotificationScheduler";
    private final Context context;
    private final AlarmManager alarmManager;
    
    public DailyNotificationScheduler(Context context) {
        this.context = context;
        this.alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    }
    
    public void scheduleNotification(NotificationContent content, int hour, int minute) {
        try {
            // Create intent for notification
            Intent intent = new Intent(context, DailyNotificationReceiver.class);
            intent.putExtra("notification_id", content.getId());
            intent.putExtra("title", content.getTitle());
            intent.putExtra("body", content.getBody());
            intent.putExtra("sound", content.isSound());
            intent.putExtra("priority", content.getPriority());
            intent.putExtra("url", content.getUrl());
            
            PendingIntent pendingIntent = PendingIntent.getBroadcast(
                context, 
                content.getId().hashCode(), 
                intent, 
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
            );
            
            // Calculate trigger 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);
            }
            
            // Schedule exact alarm
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                alarmManager.setExactAndAllowWhileIdle(
                    AlarmManager.RTC_WAKEUP,
                    calendar.getTimeInMillis(),
                    pendingIntent
                );
            } else {
                alarmManager.setExact(
                    AlarmManager.RTC_WAKEUP,
                    calendar.getTimeInMillis(),
                    pendingIntent
                );
            }
            
            Log.d(TAG, "Scheduled notification for " + hour + ":" + minute + " (ID: " + content.getId() + ")");
            
        } catch (Exception e) {
            Log.e(TAG, "Error scheduling notification", e);
            throw e;
        }
    }
    
    public boolean isNotificationScheduled(String notificationId) {
        Intent intent = new Intent(context, DailyNotificationReceiver.class);
        intent.putExtra("notification_id", notificationId);
        
        PendingIntent pendingIntent = PendingIntent.getBroadcast(
            context, 
            notificationId.hashCode(), 
            intent, 
            PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE
        );
        
        return pendingIntent != null;
    }
}

📱 Android Manifest Configuration

AndroidManifest.xml - Key Sections

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
    <!-- Permissions -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
    <application>
        
        <!-- Boot Receiver -->
        <receiver
            android:name="com.timesafari.dailynotification.BootReceiver"
            android:enabled="true"
            android:exported="true"
            android:directBootAware="true">
            <intent-filter android:priority="1000">
                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
            </intent-filter>
        </receiver>
        
        <!-- Notification Receiver -->
        <receiver
            android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
            android:enabled="true"
            android:exported="false">
        </receiver>
        
    </application>
</manifest>

🧪 Test App JavaScript

Test App - Core Functions

function testPlugin() {
    const status = document.getElementById('status');
    status.innerHTML = 'Testing plugin...';
    status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
    
    try {
        if (!window.DailyNotification) {
            status.innerHTML = 'DailyNotification plugin not available';
            status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
            return;
        }
        // Plugin is loaded and ready
        status.innerHTML = 'Plugin is loaded and ready!';
        status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
    } catch (error) {
        status.innerHTML = `Plugin test failed: ${error.message}`;
        status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
    }
}

function testNotification() {
    const status = document.getElementById('status');
    status.innerHTML = 'Testing notification...';
    status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
    
    try {
        if (!window.DailyNotification) {
            status.innerHTML = 'DailyNotification plugin not available';
            status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
            return;
        }
        
        // Test the notification method directly
        console.log('Testing notification scheduling...');
        const now = new Date();
        const testTime = new Date(now.getTime() + 300000); // 5 minutes from now
        const timeString = testTime.getHours().toString().padStart(2, '0') + ':' + 
                         testTime.getMinutes().toString().padStart(2, '0');
        
        window.DailyNotification.scheduleDailyNotification({
            time: timeString,
            title: 'Test Notification',
            body: 'This is a test notification from the DailyNotification plugin!',
            sound: true,
            priority: 'high'
        })
        .then(() => {
            status.innerHTML = 'Notification scheduled for ' + timeString + '! Check your notification bar in 5 minutes.';
            status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
        })
        .catch(error => {
            status.innerHTML = `Notification failed: ${error.message}`;
            status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
        });
    } catch (error) {
        status.innerHTML = `Notification test failed: ${error.message}`;
        status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
    }
}

These code snippets provide ChatGPT with the essential implementation details for comprehensive analysis and improvement recommendations.