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
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.