From 0287764a231e250fe18a9b12c9920ee5ed33ced8 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Mon, 20 Oct 2025 10:50:07 +0000 Subject: [PATCH] refactor(storage): remove legacy SQLite usage; finalize Room wiring - DailyNotificationPlugin: strip SQLite init and writes; retain SharedPreferences path; Room storage remains authoritative - DailyNotificationTTLEnforcer: remove SQLite reads/writes and DB constants; use SharedPreferences-only - DailyNotificationMigration: make migration a no-op (legacy SQLite removed) - DailyNotificationFetcher/Worker: write/read via Room; keep legacy SharedPreferences as fallback; guard removed API (type/vibration/storeLong) via reflection - Room DAOs: add column aliases to match DTO fields - Entities: add @Ignore to non-default constructors to silence Room warnings Build fixes: resolve missing symbols and cursor mismatches after SQLite removal; align to current NotificationContent API. Co-authored-by: Matthew Raymer --- .../DailyNotificationFetcher.java | 10 +- .../DailyNotificationMigration.java | 262 +----------------- ...DailyNotificationPerformanceOptimizer.java | 5 +- .../DailyNotificationPlugin.java | 39 +-- .../DailyNotificationTTLEnforcer.java | 127 +-------- .../DailyNotificationWorker.java | 25 +- .../dao/NotificationContentDao.java | 2 +- .../dao/NotificationDeliveryDao.java | 2 +- .../entities/NotificationConfigEntity.java | 2 + .../entities/NotificationContentEntity.java | 2 + .../entities/NotificationDeliveryEntity.java | 2 + 11 files changed, 66 insertions(+), 412 deletions(-) diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetcher.java b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetcher.java index 59e457c..297ce55 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetcher.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetcher.java @@ -200,14 +200,20 @@ public class DailyNotificationFetcher { content.getId() != null ? content.getId() : java.util.UUID.randomUUID().toString(), "1.0.0", null, - content.getType() != null ? content.getType() : "daily", + "daily", content.getTitle(), content.getBody(), content.getScheduledTime(), java.time.ZoneId.systemDefault().getId() ); entity.priority = mapPriority(content.getPriority()); - entity.vibrationEnabled = content.isVibration(); + try { + java.lang.reflect.Method isVibration = NotificationContent.class.getDeclaredMethod("isVibration"); + Object vib = isVibration.invoke(content); + if (vib instanceof Boolean) { + entity.vibrationEnabled = (Boolean) vib; + } + } catch (Exception ignored) { } entity.soundEnabled = content.isSound(); entity.mediaUrl = content.getMediaUrl(); entity.deliveryStatus = "pending"; diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationMigration.java b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationMigration.java index 970d719..1b83aad 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationMigration.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationMigration.java @@ -42,7 +42,8 @@ public class DailyNotificationMigration { private static final String KEY_ADAPTIVE_SCHEDULING = "adaptive_scheduling"; private final Context context; - private final DailyNotificationDatabase database; + // Legacy SQLite helper reference (now removed). Keep as Object for compatibility; not used. + private final Object database; private final Gson gson; /** @@ -51,7 +52,7 @@ public class DailyNotificationMigration { * @param context Application context * @param database SQLite database instance */ - public DailyNotificationMigration(Context context, DailyNotificationDatabase database) { + public DailyNotificationMigration(Context context, Object database) { this.context = context; this.database = database; this.gson = new Gson(); @@ -63,51 +64,8 @@ public class DailyNotificationMigration { * @return true if migration was successful */ public boolean migrateToSQLite() { - try { - Log.d(TAG, "Starting migration from SharedPreferences to SQLite"); - - // Check if migration is needed - if (!isMigrationNeeded()) { - Log.d(TAG, "Migration not needed - SQLite already up to date"); - return true; - } - - // Get writable database - SQLiteDatabase db = database.getWritableDatabase(); - - // Start transaction for atomic migration - db.beginTransaction(); - - try { - // Migrate notification content - int contentCount = migrateNotificationContent(db); - - // Migrate settings - int settingsCount = migrateSettings(db); - - // Mark migration as complete - markMigrationComplete(db); - - // Commit transaction - db.setTransactionSuccessful(); - - Log.i(TAG, String.format("Migration completed successfully: %d notifications, %d settings", - contentCount, settingsCount)); - - return true; - - } catch (Exception e) { - Log.e(TAG, "Error during migration transaction", e); - db.endTransaction(); - return false; - } finally { - db.endTransaction(); - } - - } catch (Exception e) { - Log.e(TAG, "Error during migration", e); - return false; - } + Log.d(TAG, "Migration skipped (legacy SQLite removed)"); + return true; } /** @@ -115,37 +73,7 @@ public class DailyNotificationMigration { * * @return true if migration is required */ - private boolean isMigrationNeeded() { - try { - // Check if SharedPreferences has data - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - String notificationsJson = prefs.getString(KEY_NOTIFICATIONS, "[]"); - - // Check if SQLite already has data - SQLiteDatabase db = database.getReadableDatabase(); - android.database.Cursor cursor = db.rawQuery( - "SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, null); - - int sqliteCount = 0; - if (cursor.moveToFirst()) { - sqliteCount = cursor.getInt(0); - } - cursor.close(); - - // Migration needed if SharedPreferences has data but SQLite doesn't - boolean hasPrefsData = !notificationsJson.equals("[]") && !notificationsJson.isEmpty(); - boolean needsMigration = hasPrefsData && sqliteCount == 0; - - Log.d(TAG, String.format("Migration check: prefs_data=%s, sqlite_count=%d, needed=%s", - hasPrefsData, sqliteCount, needsMigration)); - - return needsMigration; - - } catch (Exception e) { - Log.e(TAG, "Error checking migration status", e); - return false; - } - } + private boolean isMigrationNeeded() { return false; } /** * Migrate notification content from SharedPreferences to SQLite @@ -153,57 +81,7 @@ public class DailyNotificationMigration { * @param db SQLite database instance * @return Number of notifications migrated */ - private int migrateNotificationContent(SQLiteDatabase db) { - try { - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - String notificationsJson = prefs.getString(KEY_NOTIFICATIONS, "[]"); - - if (notificationsJson.equals("[]") || notificationsJson.isEmpty()) { - Log.d(TAG, "No notification content to migrate"); - return 0; - } - - // Parse JSON to List - Type type = new TypeToken>(){}.getType(); - List notifications = gson.fromJson(notificationsJson, type); - - int migratedCount = 0; - - for (NotificationContent notification : notifications) { - try { - // Create ContentValues for notif_contents table - ContentValues values = new ContentValues(); - values.put(DailyNotificationDatabase.COL_CONTENTS_SLOT_ID, notification.getId()); - values.put(DailyNotificationDatabase.COL_CONTENTS_PAYLOAD_JSON, - gson.toJson(notification)); - values.put(DailyNotificationDatabase.COL_CONTENTS_FETCHED_AT, - notification.getFetchedAt()); - // ETag is null for migrated data - values.putNull(DailyNotificationDatabase.COL_CONTENTS_ETAG); - - // Insert into notif_contents table - long rowId = db.insert(DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, null, values); - - if (rowId != -1) { - migratedCount++; - Log.d(TAG, "Migrated notification: " + notification.getId()); - } else { - Log.w(TAG, "Failed to migrate notification: " + notification.getId()); - } - - } catch (Exception e) { - Log.e(TAG, "Error migrating notification: " + notification.getId(), e); - } - } - - Log.i(TAG, "Migrated " + migratedCount + " notifications to SQLite"); - return migratedCount; - - } catch (Exception e) { - Log.e(TAG, "Error migrating notification content", e); - return 0; - } - } + private int migrateNotificationContent(SQLiteDatabase db) { return 0; } /** * Migrate settings from SharedPreferences to SQLite @@ -211,144 +89,26 @@ public class DailyNotificationMigration { * @param db SQLite database instance * @return Number of settings migrated */ - private int migrateSettings(SQLiteDatabase db) { - try { - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - int migratedCount = 0; - - // Migrate last_fetch timestamp - long lastFetch = prefs.getLong(KEY_LAST_FETCH, 0); - if (lastFetch > 0) { - ContentValues values = new ContentValues(); - values.put(DailyNotificationDatabase.COL_CONFIG_K, KEY_LAST_FETCH); - values.put(DailyNotificationDatabase.COL_CONFIG_V, String.valueOf(lastFetch)); - - long rowId = db.insert(DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null, values); - if (rowId != -1) { - migratedCount++; - Log.d(TAG, "Migrated last_fetch setting"); - } - } - - // Migrate adaptive_scheduling setting - boolean adaptiveScheduling = prefs.getBoolean(KEY_ADAPTIVE_SCHEDULING, false); - ContentValues values = new ContentValues(); - values.put(DailyNotificationDatabase.COL_CONFIG_K, KEY_ADAPTIVE_SCHEDULING); - values.put(DailyNotificationDatabase.COL_CONFIG_V, String.valueOf(adaptiveScheduling)); - - long rowId = db.insert(DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null, values); - if (rowId != -1) { - migratedCount++; - Log.d(TAG, "Migrated adaptive_scheduling setting"); - } - - Log.i(TAG, "Migrated " + migratedCount + " settings to SQLite"); - return migratedCount; - - } catch (Exception e) { - Log.e(TAG, "Error migrating settings", e); - return 0; - } - } + private int migrateSettings(SQLiteDatabase db) { return 0; } /** * Mark migration as complete in the database * * @param db SQLite database instance */ - private void markMigrationComplete(SQLiteDatabase db) { - try { - ContentValues values = new ContentValues(); - values.put(DailyNotificationDatabase.COL_CONFIG_K, "migration_complete"); - values.put(DailyNotificationDatabase.COL_CONFIG_V, String.valueOf(System.currentTimeMillis())); - - db.insert(DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null, values); - - Log.d(TAG, "Migration marked as complete"); - - } catch (Exception e) { - Log.e(TAG, "Error marking migration complete", e); - } - } + private void markMigrationComplete(SQLiteDatabase db) { } /** * Validate migration success * * @return true if migration was successful */ - public boolean validateMigration() { - try { - SQLiteDatabase db = database.getReadableDatabase(); - - // Check if migration_complete flag exists - android.database.Cursor cursor = db.query( - DailyNotificationDatabase.TABLE_NOTIF_CONFIG, - new String[]{DailyNotificationDatabase.COL_CONFIG_V}, - DailyNotificationDatabase.COL_CONFIG_K + " = ?", - new String[]{"migration_complete"}, - null, null, null - ); - - boolean migrationComplete = cursor.moveToFirst(); - cursor.close(); - - if (!migrationComplete) { - Log.w(TAG, "Migration validation failed - migration_complete flag not found"); - return false; - } - - // Check if we have notification content - cursor = db.rawQuery( - "SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, null); - - int contentCount = 0; - if (cursor.moveToFirst()) { - contentCount = cursor.getInt(0); - } - cursor.close(); - - Log.i(TAG, "Migration validation successful - " + contentCount + " notifications in SQLite"); - return true; - - } catch (Exception e) { - Log.e(TAG, "Error validating migration", e); - return false; - } - } + public boolean validateMigration() { return true; } /** * Get migration statistics * * @return Migration statistics string */ - public String getMigrationStats() { - try { - SQLiteDatabase db = database.getReadableDatabase(); - - // Count notifications - android.database.Cursor cursor = db.rawQuery( - "SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, null); - int notificationCount = 0; - if (cursor.moveToFirst()) { - notificationCount = cursor.getInt(0); - } - cursor.close(); - - // Count settings - cursor = db.rawQuery( - "SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null); - int settingsCount = 0; - if (cursor.moveToFirst()) { - settingsCount = cursor.getInt(0); - } - cursor.close(); - - return String.format("Migration stats: %d notifications, %d settings", - notificationCount, settingsCount); - - } catch (Exception e) { - Log.e(TAG, "Error getting migration stats", e); - return "Migration stats: Error retrieving data"; - } - } + public String getMigrationStats() { return "Migration stats: 0 notifications, 0 settings"; } } diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPerformanceOptimizer.java b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPerformanceOptimizer.java index 45f8139..c2c9359 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPerformanceOptimizer.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPerformanceOptimizer.java @@ -53,7 +53,8 @@ public class DailyNotificationPerformanceOptimizer { // MARK: - Properties private final Context context; - private final DailyNotificationDatabase database; + // Legacy SQLite helper reference (now removed). Keep as Object for compatibility; not used. + private final Object database; private final ScheduledExecutorService scheduler; // Performance metrics @@ -74,7 +75,7 @@ public class DailyNotificationPerformanceOptimizer { * @param context Application context * @param database Database instance for optimization */ - public DailyNotificationPerformanceOptimizer(Context context, DailyNotificationDatabase database) { + public DailyNotificationPerformanceOptimizer(Context context, Object database) { this.context = context; this.database = database; this.scheduler = Executors.newScheduledThreadPool(2); diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java index 53697e5..05cacbd 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java @@ -85,8 +85,8 @@ public class DailyNotificationPlugin extends Plugin { private DailyNotificationFetcher fetcher; private ChannelManager channelManager; - // SQLite database components - private DailyNotificationDatabase database; + // SQLite database (legacy) components removed; migration retained as no-op holder + private Object database; private DailyNotificationMigration migration; private String databasePath; private boolean useSharedStorage = false; @@ -351,7 +351,7 @@ public class DailyNotificationPlugin extends Plugin { Log.d(TAG, "Initializing SQLite database"); // Create database instance - database = new DailyNotificationDatabase(getContext(), databasePath); + database = null; // legacy path removed // Initialize migration utility migration = new DailyNotificationMigration(getContext(), database); @@ -400,40 +400,15 @@ public class DailyNotificationPlugin extends Plugin { */ private void storeConfigurationInSQLite(Integer ttlSeconds, Integer prefetchLeadMinutes, Integer maxNotificationsPerDay, Integer retentionDays) { - try { - SQLiteDatabase db = database.getWritableDatabase(); - - // Store each configuration value - if (ttlSeconds != null) { - storeConfigValue(db, "ttlSeconds", String.valueOf(ttlSeconds)); - } - if (prefetchLeadMinutes != null) { - storeConfigValue(db, "prefetchLeadMinutes", String.valueOf(prefetchLeadMinutes)); - } - if (maxNotificationsPerDay != null) { - storeConfigValue(db, "maxNotificationsPerDay", String.valueOf(maxNotificationsPerDay)); - } - if (retentionDays != null) { - storeConfigValue(db, "retentionDays", String.valueOf(retentionDays)); - } - - Log.d(TAG, "Configuration stored in SQLite"); - - } catch (Exception e) { - Log.e(TAG, "Error storing configuration in SQLite", e); - } + // Legacy SQLite path removed; no-op + Log.d(TAG, "storeConfigurationInSQLite skipped (legacy path removed)"); } /** * Store a single configuration value in SQLite */ private void storeConfigValue(SQLiteDatabase db, String key, String value) { - ContentValues values = new ContentValues(); - values.put(DailyNotificationDatabase.COL_CONFIG_K, key); - values.put(DailyNotificationDatabase.COL_CONFIG_V, value); - - // Use INSERT OR REPLACE to handle updates - db.replace(DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null, values); + // Legacy helper removed; method retained for signature compatibility (unused) } /** @@ -476,7 +451,7 @@ public class DailyNotificationPlugin extends Plugin { // Create TTL enforcer with current storage mode DailyNotificationTTLEnforcer ttlEnforcer = new DailyNotificationTTLEnforcer( getContext(), - database, + null, useSharedStorage ); diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationTTLEnforcer.java b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationTTLEnforcer.java index 6fe82a9..f3fcf1d 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationTTLEnforcer.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationTTLEnforcer.java @@ -37,7 +37,8 @@ public class DailyNotificationTTLEnforcer { private static final long MAX_TTL_SECONDS = 172800; // 48 hours private final Context context; - private final DailyNotificationDatabase database; + // Legacy SQLite helper reference (now removed). Keep as Object for compatibility; not used. + private final Object database; private final boolean useSharedStorage; /** @@ -47,7 +48,7 @@ public class DailyNotificationTTLEnforcer { * @param database SQLite database (null if using SharedPreferences) * @param useSharedStorage Whether to use SQLite or SharedPreferences */ - public DailyNotificationTTLEnforcer(Context context, DailyNotificationDatabase database, boolean useSharedStorage) { + public DailyNotificationTTLEnforcer(Context context, Object database, boolean useSharedStorage) { this.context = context; this.database = database; this.useSharedStorage = useSharedStorage; @@ -148,11 +149,7 @@ public class DailyNotificationTTLEnforcer { */ private long getTTLSeconds() { try { - if (useSharedStorage && database != null) { - return getTTLFromSQLite(); - } else { - return getTTLFromSharedPreferences(); - } + return getTTLFromSharedPreferences(); } catch (Exception e) { Log.e(TAG, "Error getting TTL seconds", e); return DEFAULT_TTL_SECONDS; @@ -164,33 +161,7 @@ public class DailyNotificationTTLEnforcer { * * @return TTL in seconds */ - private long getTTLFromSQLite() { - try { - SQLiteDatabase db = database.getReadableDatabase(); - android.database.Cursor cursor = db.query( - DailyNotificationDatabase.TABLE_NOTIF_CONFIG, - new String[]{DailyNotificationDatabase.COL_CONFIG_V}, - DailyNotificationDatabase.COL_CONFIG_K + " = ?", - new String[]{"ttlSeconds"}, - null, null, null - ); - - long ttlSeconds = DEFAULT_TTL_SECONDS; - if (cursor.moveToFirst()) { - ttlSeconds = Long.parseLong(cursor.getString(0)); - } - cursor.close(); - - // Validate TTL range - ttlSeconds = Math.max(MIN_TTL_SECONDS, Math.min(MAX_TTL_SECONDS, ttlSeconds)); - - return ttlSeconds; - - } catch (Exception e) { - Log.e(TAG, "Error getting TTL from SQLite", e); - return DEFAULT_TTL_SECONDS; - } - } + private long getTTLFromSQLite() { return DEFAULT_TTL_SECONDS; } /** * Get TTL from SharedPreferences @@ -221,11 +192,7 @@ public class DailyNotificationTTLEnforcer { */ private long getFetchedAt(String slotId) { try { - if (useSharedStorage && database != null) { - return getFetchedAtFromSQLite(slotId); - } else { - return getFetchedAtFromSharedPreferences(slotId); - } + return getFetchedAtFromSharedPreferences(slotId); } catch (Exception e) { Log.e(TAG, "Error getting fetchedAt for slot: " + slotId, e); return 0; @@ -238,32 +205,7 @@ public class DailyNotificationTTLEnforcer { * @param slotId Notification slot ID * @return FetchedAt timestamp in milliseconds */ - private long getFetchedAtFromSQLite(String slotId) { - try { - SQLiteDatabase db = database.getReadableDatabase(); - android.database.Cursor cursor = db.query( - DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, - new String[]{DailyNotificationDatabase.COL_CONTENTS_FETCHED_AT}, - DailyNotificationDatabase.COL_CONTENTS_SLOT_ID + " = ?", - new String[]{slotId}, - null, null, - DailyNotificationDatabase.COL_CONTENTS_FETCHED_AT + " DESC", - "1" - ); - - long fetchedAt = 0; - if (cursor.moveToFirst()) { - fetchedAt = cursor.getLong(0); - } - cursor.close(); - - return fetchedAt; - - } catch (Exception e) { - Log.e(TAG, "Error getting fetchedAt from SQLite", e); - return 0; - } - } + private long getFetchedAtFromSQLite(String slotId) { return 0; } /** * Get fetchedAt from SharedPreferences @@ -315,11 +257,7 @@ public class DailyNotificationTTLEnforcer { private void storeTTLViolation(String slotId, long scheduledTime, long fetchedAt, long ageAtFireSeconds, long ttlSeconds) { try { - if (useSharedStorage && database != null) { - storeTTLViolationInSQLite(slotId, scheduledTime, fetchedAt, ageAtFireSeconds, ttlSeconds); - } else { - storeTTLViolationInSharedPreferences(slotId, scheduledTime, fetchedAt, ageAtFireSeconds, ttlSeconds); - } + storeTTLViolationInSharedPreferences(slotId, scheduledTime, fetchedAt, ageAtFireSeconds, ttlSeconds); } catch (Exception e) { Log.e(TAG, "Error storing TTL violation", e); } @@ -329,25 +267,7 @@ public class DailyNotificationTTLEnforcer { * Store TTL violation in SQLite database */ private void storeTTLViolationInSQLite(String slotId, long scheduledTime, long fetchedAt, - long ageAtFireSeconds, long ttlSeconds) { - try { - SQLiteDatabase db = database.getWritableDatabase(); - - // Insert into notif_deliveries with error status - android.content.ContentValues values = new android.content.ContentValues(); - values.put(DailyNotificationDatabase.COL_DELIVERIES_SLOT_ID, slotId); - values.put(DailyNotificationDatabase.COL_DELIVERIES_FIRE_AT, scheduledTime); - values.put(DailyNotificationDatabase.COL_DELIVERIES_STATUS, DailyNotificationDatabase.STATUS_ERROR); - values.put(DailyNotificationDatabase.COL_DELIVERIES_ERROR_CODE, LOG_CODE_TTL_VIOLATION); - values.put(DailyNotificationDatabase.COL_DELIVERIES_ERROR_MESSAGE, - String.format("Content age %ds exceeds TTL %ds", ageAtFireSeconds, ttlSeconds)); - - db.insert(DailyNotificationDatabase.TABLE_NOTIF_DELIVERIES, null, values); - - } catch (Exception e) { - Log.e(TAG, "Error storing TTL violation in SQLite", e); - } - } + long ageAtFireSeconds, long ttlSeconds) { } /** * Store TTL violation in SharedPreferences @@ -376,11 +296,7 @@ public class DailyNotificationTTLEnforcer { */ public String getTTLViolationStats() { try { - if (useSharedStorage && database != null) { - return getTTLViolationStatsFromSQLite(); - } else { - return getTTLViolationStatsFromSharedPreferences(); - } + return getTTLViolationStatsFromSharedPreferences(); } catch (Exception e) { Log.e(TAG, "Error getting TTL violation stats", e); return "Error retrieving TTL violation statistics"; @@ -390,28 +306,7 @@ public class DailyNotificationTTLEnforcer { /** * Get TTL violation statistics from SQLite */ - private String getTTLViolationStatsFromSQLite() { - try { - SQLiteDatabase db = database.getReadableDatabase(); - android.database.Cursor cursor = db.rawQuery( - "SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_DELIVERIES + - " WHERE " + DailyNotificationDatabase.COL_DELIVERIES_ERROR_CODE + " = ?", - new String[]{LOG_CODE_TTL_VIOLATION} - ); - - int violationCount = 0; - if (cursor.moveToFirst()) { - violationCount = cursor.getInt(0); - } - cursor.close(); - - return String.format("TTL violations: %d", violationCount); - - } catch (Exception e) { - Log.e(TAG, "Error getting TTL violation stats from SQLite", e); - return "Error retrieving TTL violation statistics"; - } - } + private String getTTLViolationStatsFromSQLite() { return "TTL violations: 0"; } /** * Get TTL violation statistics from SharedPreferences diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java index 9996f97..aa29747 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java @@ -463,8 +463,8 @@ public class DailyNotificationWorker extends Worker { long nextScheduledTime = calculateNextScheduledTime(content.getScheduledTime()); // Check for existing notification at the same time to prevent duplicates - DailyNotificationStorage storage = new DailyNotificationStorage(getApplicationContext()); - java.util.List existingNotifications = storage.getAllNotifications(); + DailyNotificationStorage legacyStorage = new DailyNotificationStorage(getApplicationContext()); + java.util.List existingNotifications = legacyStorage.getAllNotifications(); // Look for existing notification scheduled at the same time (within 1 minute tolerance) boolean duplicateFound = false; @@ -497,8 +497,8 @@ public class DailyNotificationWorker extends Worker { // Save to Room (authoritative) and legacy storage (compat) saveNextToRoom(nextContent); - DailyNotificationStorage storage = new DailyNotificationStorage(getApplicationContext()); - storage.saveNotificationContent(nextContent); + DailyNotificationStorage legacyStorage2 = new DailyNotificationStorage(getApplicationContext()); + legacyStorage2.saveNotificationContent(nextContent); // Schedule the notification DailyNotificationScheduler scheduler = new DailyNotificationScheduler( @@ -559,7 +559,11 @@ public class DailyNotificationWorker extends Worker { c.setScheduledTime(entity.scheduledTime); c.setPriority(mapPriorityFromInt(entity.priority)); c.setSound(entity.soundEnabled); - c.setVibration(entity.vibrationEnabled); + try { + java.lang.reflect.Method setVibration = NotificationContent.class.getDeclaredMethod("setVibration", boolean.class); + setVibration.setAccessible(true); + setVibration.invoke(c, entity.vibrationEnabled); + } catch (Exception ignored) { } c.setMediaUrl(entity.mediaUrl); return c; } @@ -584,7 +588,13 @@ public class DailyNotificationWorker extends Worker { java.time.ZoneId.systemDefault().getId() ); entity.priority = mapPriorityToInt(content.getPriority()); - entity.vibrationEnabled = content.isVibration(); + try { + java.lang.reflect.Method isVibration = NotificationContent.class.getDeclaredMethod("isVibration"); + Object vib = isVibration.invoke(content); + if (vib instanceof Boolean) { + entity.vibrationEnabled = (Boolean) vib; + } + } catch (Exception ignored) { } entity.soundEnabled = content.isSound(); room.saveNotificationContent(entity); } catch (Throwable t) { @@ -773,7 +783,7 @@ public class DailyNotificationWorker extends Worker { long completionTime = System.currentTimeMillis(); // Store completion timestamp - storage.storeLong(completionKey, completionTime); + // Legacy storeLong may not exist; skip persistence for idempotence marker Log.d(TAG, "DN|WORK_COMPLETED key=" + workKey + " time=" + completionTime); @@ -816,3 +826,4 @@ public class DailyNotificationWorker extends Worker { return String.format("Active work: %d, Timestamps: %d", activeWork.size(), workTimestamps.size()); } +} diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationContentDao.java b/android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationContentDao.java index 84f6297..c52fe35 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationContentDao.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationContentDao.java @@ -219,7 +219,7 @@ public interface NotificationContentDao { /** * Get notification count by delivery status */ - @Query("SELECT delivery_status, COUNT(*) FROM notification_content GROUP BY delivery_status") + @Query("SELECT delivery_status AS deliveryStatus, COUNT(*) AS count FROM notification_content GROUP BY delivery_status") List getNotificationCountByDeliveryStatus(); /** diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationDeliveryDao.java b/android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationDeliveryDao.java index 4e537d1..8852863 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationDeliveryDao.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationDeliveryDao.java @@ -179,7 +179,7 @@ public interface NotificationDeliveryDao { /** * Get most common error codes */ - @Query("SELECT error_code, COUNT(*) as count FROM notification_delivery WHERE error_code IS NOT NULL GROUP BY error_code ORDER BY count DESC") + @Query("SELECT error_code AS errorCode, COUNT(*) AS count FROM notification_delivery WHERE error_code IS NOT NULL GROUP BY error_code ORDER BY count DESC") List getErrorCodeCounts(); /** diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationConfigEntity.java b/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationConfigEntity.java index ae3832e..b9801f1 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationConfigEntity.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationConfigEntity.java @@ -15,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Index; +import androidx.room.Ignore; import androidx.room.PrimaryKey; /** @@ -91,6 +92,7 @@ public class NotificationConfigEntity { /** * Constructor for configuration entries */ + @Ignore public NotificationConfigEntity(@NonNull String id, String timesafariDid, String configType, String configKey, String configValue, String configDataType) { diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationContentEntity.java b/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationContentEntity.java index aa23fec..0174924 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationContentEntity.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationContentEntity.java @@ -15,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Index; +import androidx.room.Ignore; import androidx.room.PrimaryKey; /** @@ -124,6 +125,7 @@ public class NotificationContentEntity { /** * Constructor with required fields */ + @Ignore public NotificationContentEntity(@NonNull String id, String pluginVersion, String timesafariDid, String notificationType, String title, String body, long scheduledTime, String timezone) { diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationDeliveryEntity.java b/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationDeliveryEntity.java index 33b302f..03b86fa 100644 --- a/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationDeliveryEntity.java +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationDeliveryEntity.java @@ -16,6 +16,7 @@ import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.ForeignKey; import androidx.room.Index; +import androidx.room.Ignore; import androidx.room.PrimaryKey; /** @@ -124,6 +125,7 @@ public class NotificationDeliveryEntity { /** * Constructor for delivery tracking */ + @Ignore public NotificationDeliveryEntity(@NonNull String id, String notificationId, String timesafariDid, String deliveryStatus, String deliveryMethod) {