Browse Source

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
master
Matthew Raymer 2 days ago
parent
commit
0287764a23
  1. 10
      android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetcher.java
  2. 262
      android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationMigration.java
  3. 5
      android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPerformanceOptimizer.java
  4. 39
      android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java
  5. 127
      android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationTTLEnforcer.java
  6. 25
      android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java
  7. 2
      android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationContentDao.java
  8. 2
      android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationDeliveryDao.java
  9. 2
      android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationConfigEntity.java
  10. 2
      android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationContentEntity.java
  11. 2
      android/plugin/src/main/java/com/timesafari/dailynotification/entities/NotificationDeliveryEntity.java

10
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(), content.getId() != null ? content.getId() : java.util.UUID.randomUUID().toString(),
"1.0.0", "1.0.0",
null, null,
content.getType() != null ? content.getType() : "daily", "daily",
content.getTitle(), content.getTitle(),
content.getBody(), content.getBody(),
content.getScheduledTime(), content.getScheduledTime(),
java.time.ZoneId.systemDefault().getId() java.time.ZoneId.systemDefault().getId()
); );
entity.priority = mapPriority(content.getPriority()); 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.soundEnabled = content.isSound();
entity.mediaUrl = content.getMediaUrl(); entity.mediaUrl = content.getMediaUrl();
entity.deliveryStatus = "pending"; entity.deliveryStatus = "pending";

262
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 static final String KEY_ADAPTIVE_SCHEDULING = "adaptive_scheduling";
private final Context context; 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; private final Gson gson;
/** /**
@ -51,7 +52,7 @@ public class DailyNotificationMigration {
* @param context Application context * @param context Application context
* @param database SQLite database instance * @param database SQLite database instance
*/ */
public DailyNotificationMigration(Context context, DailyNotificationDatabase database) { public DailyNotificationMigration(Context context, Object database) {
this.context = context; this.context = context;
this.database = database; this.database = database;
this.gson = new Gson(); this.gson = new Gson();
@ -63,51 +64,8 @@ public class DailyNotificationMigration {
* @return true if migration was successful * @return true if migration was successful
*/ */
public boolean migrateToSQLite() { public boolean migrateToSQLite() {
try { Log.d(TAG, "Migration skipped (legacy SQLite removed)");
Log.d(TAG, "Starting migration from SharedPreferences to SQLite"); return true;
// 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;
}
} }
/** /**
@ -115,37 +73,7 @@ public class DailyNotificationMigration {
* *
* @return true if migration is required * @return true if migration is required
*/ */
private boolean isMigrationNeeded() { private boolean isMigrationNeeded() { return false; }
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;
}
}
/** /**
* Migrate notification content from SharedPreferences to SQLite * Migrate notification content from SharedPreferences to SQLite
@ -153,57 +81,7 @@ public class DailyNotificationMigration {
* @param db SQLite database instance * @param db SQLite database instance
* @return Number of notifications migrated * @return Number of notifications migrated
*/ */
private int migrateNotificationContent(SQLiteDatabase db) { private int migrateNotificationContent(SQLiteDatabase db) { return 0; }
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<NotificationContent>
Type type = new TypeToken<ArrayList<NotificationContent>>(){}.getType();
List<NotificationContent> 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;
}
}
/** /**
* Migrate settings from SharedPreferences to SQLite * Migrate settings from SharedPreferences to SQLite
@ -211,144 +89,26 @@ public class DailyNotificationMigration {
* @param db SQLite database instance * @param db SQLite database instance
* @return Number of settings migrated * @return Number of settings migrated
*/ */
private int migrateSettings(SQLiteDatabase db) { private int migrateSettings(SQLiteDatabase db) { return 0; }
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;
}
}
/** /**
* Mark migration as complete in the database * Mark migration as complete in the database
* *
* @param db SQLite database instance * @param db SQLite database instance
*/ */
private void markMigrationComplete(SQLiteDatabase db) { 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);
}
}
/** /**
* Validate migration success * Validate migration success
* *
* @return true if migration was successful * @return true if migration was successful
*/ */
public boolean validateMigration() { public boolean validateMigration() { return true; }
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;
}
}
/** /**
* Get migration statistics * Get migration statistics
* *
* @return Migration statistics string * @return Migration statistics string
*/ */
public String getMigrationStats() { public String getMigrationStats() { return "Migration stats: 0 notifications, 0 settings"; }
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";
}
}
} }

5
android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPerformanceOptimizer.java

@ -53,7 +53,8 @@ public class DailyNotificationPerformanceOptimizer {
// MARK: - Properties // MARK: - Properties
private final Context context; 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; private final ScheduledExecutorService scheduler;
// Performance metrics // Performance metrics
@ -74,7 +75,7 @@ public class DailyNotificationPerformanceOptimizer {
* @param context Application context * @param context Application context
* @param database Database instance for optimization * @param database Database instance for optimization
*/ */
public DailyNotificationPerformanceOptimizer(Context context, DailyNotificationDatabase database) { public DailyNotificationPerformanceOptimizer(Context context, Object database) {
this.context = context; this.context = context;
this.database = database; this.database = database;
this.scheduler = Executors.newScheduledThreadPool(2); this.scheduler = Executors.newScheduledThreadPool(2);

39
android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java

@ -85,8 +85,8 @@ public class DailyNotificationPlugin extends Plugin {
private DailyNotificationFetcher fetcher; private DailyNotificationFetcher fetcher;
private ChannelManager channelManager; private ChannelManager channelManager;
// SQLite database components // SQLite database (legacy) components removed; migration retained as no-op holder
private DailyNotificationDatabase database; private Object database;
private DailyNotificationMigration migration; private DailyNotificationMigration migration;
private String databasePath; private String databasePath;
private boolean useSharedStorage = false; private boolean useSharedStorage = false;
@ -351,7 +351,7 @@ public class DailyNotificationPlugin extends Plugin {
Log.d(TAG, "Initializing SQLite database"); Log.d(TAG, "Initializing SQLite database");
// Create database instance // Create database instance
database = new DailyNotificationDatabase(getContext(), databasePath); database = null; // legacy path removed
// Initialize migration utility // Initialize migration utility
migration = new DailyNotificationMigration(getContext(), database); migration = new DailyNotificationMigration(getContext(), database);
@ -400,40 +400,15 @@ public class DailyNotificationPlugin extends Plugin {
*/ */
private void storeConfigurationInSQLite(Integer ttlSeconds, Integer prefetchLeadMinutes, private void storeConfigurationInSQLite(Integer ttlSeconds, Integer prefetchLeadMinutes,
Integer maxNotificationsPerDay, Integer retentionDays) { Integer maxNotificationsPerDay, Integer retentionDays) {
try { // Legacy SQLite path removed; no-op
SQLiteDatabase db = database.getWritableDatabase(); Log.d(TAG, "storeConfigurationInSQLite skipped (legacy path removed)");
// 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);
}
} }
/** /**
* Store a single configuration value in SQLite * Store a single configuration value in SQLite
*/ */
private void storeConfigValue(SQLiteDatabase db, String key, String value) { private void storeConfigValue(SQLiteDatabase db, String key, String value) {
ContentValues values = new ContentValues(); // Legacy helper removed; method retained for signature compatibility (unused)
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);
} }
/** /**
@ -476,7 +451,7 @@ public class DailyNotificationPlugin extends Plugin {
// Create TTL enforcer with current storage mode // Create TTL enforcer with current storage mode
DailyNotificationTTLEnforcer ttlEnforcer = new DailyNotificationTTLEnforcer( DailyNotificationTTLEnforcer ttlEnforcer = new DailyNotificationTTLEnforcer(
getContext(), getContext(),
database, null,
useSharedStorage useSharedStorage
); );

127
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 static final long MAX_TTL_SECONDS = 172800; // 48 hours
private final Context context; 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; private final boolean useSharedStorage;
/** /**
@ -47,7 +48,7 @@ public class DailyNotificationTTLEnforcer {
* @param database SQLite database (null if using SharedPreferences) * @param database SQLite database (null if using SharedPreferences)
* @param useSharedStorage Whether to use SQLite or 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.context = context;
this.database = database; this.database = database;
this.useSharedStorage = useSharedStorage; this.useSharedStorage = useSharedStorage;
@ -148,11 +149,7 @@ public class DailyNotificationTTLEnforcer {
*/ */
private long getTTLSeconds() { private long getTTLSeconds() {
try { try {
if (useSharedStorage && database != null) { return getTTLFromSharedPreferences();
return getTTLFromSQLite();
} else {
return getTTLFromSharedPreferences();
}
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error getting TTL seconds", e); Log.e(TAG, "Error getting TTL seconds", e);
return DEFAULT_TTL_SECONDS; return DEFAULT_TTL_SECONDS;
@ -164,33 +161,7 @@ public class DailyNotificationTTLEnforcer {
* *
* @return TTL in seconds * @return TTL in seconds
*/ */
private long getTTLFromSQLite() { private long getTTLFromSQLite() { return DEFAULT_TTL_SECONDS; }
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;
}
}
/** /**
* Get TTL from SharedPreferences * Get TTL from SharedPreferences
@ -221,11 +192,7 @@ public class DailyNotificationTTLEnforcer {
*/ */
private long getFetchedAt(String slotId) { private long getFetchedAt(String slotId) {
try { try {
if (useSharedStorage && database != null) { return getFetchedAtFromSharedPreferences(slotId);
return getFetchedAtFromSQLite(slotId);
} else {
return getFetchedAtFromSharedPreferences(slotId);
}
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error getting fetchedAt for slot: " + slotId, e); Log.e(TAG, "Error getting fetchedAt for slot: " + slotId, e);
return 0; return 0;
@ -238,32 +205,7 @@ public class DailyNotificationTTLEnforcer {
* @param slotId Notification slot ID * @param slotId Notification slot ID
* @return FetchedAt timestamp in milliseconds * @return FetchedAt timestamp in milliseconds
*/ */
private long getFetchedAtFromSQLite(String slotId) { private long getFetchedAtFromSQLite(String slotId) { return 0; }
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;
}
}
/** /**
* Get fetchedAt from SharedPreferences * Get fetchedAt from SharedPreferences
@ -315,11 +257,7 @@ public class DailyNotificationTTLEnforcer {
private void storeTTLViolation(String slotId, long scheduledTime, long fetchedAt, private void storeTTLViolation(String slotId, long scheduledTime, long fetchedAt,
long ageAtFireSeconds, long ttlSeconds) { long ageAtFireSeconds, long ttlSeconds) {
try { try {
if (useSharedStorage && database != null) { storeTTLViolationInSharedPreferences(slotId, scheduledTime, fetchedAt, ageAtFireSeconds, ttlSeconds);
storeTTLViolationInSQLite(slotId, scheduledTime, fetchedAt, ageAtFireSeconds, ttlSeconds);
} else {
storeTTLViolationInSharedPreferences(slotId, scheduledTime, fetchedAt, ageAtFireSeconds, ttlSeconds);
}
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error storing TTL violation", e); Log.e(TAG, "Error storing TTL violation", e);
} }
@ -329,25 +267,7 @@ public class DailyNotificationTTLEnforcer {
* Store TTL violation in SQLite database * Store TTL violation in SQLite database
*/ */
private void storeTTLViolationInSQLite(String slotId, long scheduledTime, long fetchedAt, private void storeTTLViolationInSQLite(String slotId, long scheduledTime, long fetchedAt,
long ageAtFireSeconds, long ttlSeconds) { 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);
}
}
/** /**
* Store TTL violation in SharedPreferences * Store TTL violation in SharedPreferences
@ -376,11 +296,7 @@ public class DailyNotificationTTLEnforcer {
*/ */
public String getTTLViolationStats() { public String getTTLViolationStats() {
try { try {
if (useSharedStorage && database != null) { return getTTLViolationStatsFromSharedPreferences();
return getTTLViolationStatsFromSQLite();
} else {
return getTTLViolationStatsFromSharedPreferences();
}
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error getting TTL violation stats", e); Log.e(TAG, "Error getting TTL violation stats", e);
return "Error retrieving TTL violation statistics"; return "Error retrieving TTL violation statistics";
@ -390,28 +306,7 @@ public class DailyNotificationTTLEnforcer {
/** /**
* Get TTL violation statistics from SQLite * Get TTL violation statistics from SQLite
*/ */
private String getTTLViolationStatsFromSQLite() { private String getTTLViolationStatsFromSQLite() { return "TTL violations: 0"; }
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";
}
}
/** /**
* Get TTL violation statistics from SharedPreferences * Get TTL violation statistics from SharedPreferences

25
android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java

@ -463,8 +463,8 @@ public class DailyNotificationWorker extends Worker {
long nextScheduledTime = calculateNextScheduledTime(content.getScheduledTime()); long nextScheduledTime = calculateNextScheduledTime(content.getScheduledTime());
// Check for existing notification at the same time to prevent duplicates // Check for existing notification at the same time to prevent duplicates
DailyNotificationStorage storage = new DailyNotificationStorage(getApplicationContext()); DailyNotificationStorage legacyStorage = new DailyNotificationStorage(getApplicationContext());
java.util.List<NotificationContent> existingNotifications = storage.getAllNotifications(); java.util.List<NotificationContent> existingNotifications = legacyStorage.getAllNotifications();
// Look for existing notification scheduled at the same time (within 1 minute tolerance) // Look for existing notification scheduled at the same time (within 1 minute tolerance)
boolean duplicateFound = false; boolean duplicateFound = false;
@ -497,8 +497,8 @@ public class DailyNotificationWorker extends Worker {
// Save to Room (authoritative) and legacy storage (compat) // Save to Room (authoritative) and legacy storage (compat)
saveNextToRoom(nextContent); saveNextToRoom(nextContent);
DailyNotificationStorage storage = new DailyNotificationStorage(getApplicationContext()); DailyNotificationStorage legacyStorage2 = new DailyNotificationStorage(getApplicationContext());
storage.saveNotificationContent(nextContent); legacyStorage2.saveNotificationContent(nextContent);
// Schedule the notification // Schedule the notification
DailyNotificationScheduler scheduler = new DailyNotificationScheduler( DailyNotificationScheduler scheduler = new DailyNotificationScheduler(
@ -559,7 +559,11 @@ public class DailyNotificationWorker extends Worker {
c.setScheduledTime(entity.scheduledTime); c.setScheduledTime(entity.scheduledTime);
c.setPriority(mapPriorityFromInt(entity.priority)); c.setPriority(mapPriorityFromInt(entity.priority));
c.setSound(entity.soundEnabled); 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); c.setMediaUrl(entity.mediaUrl);
return c; return c;
} }
@ -584,7 +588,13 @@ public class DailyNotificationWorker extends Worker {
java.time.ZoneId.systemDefault().getId() java.time.ZoneId.systemDefault().getId()
); );
entity.priority = mapPriorityToInt(content.getPriority()); 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(); entity.soundEnabled = content.isSound();
room.saveNotificationContent(entity); room.saveNotificationContent(entity);
} catch (Throwable t) { } catch (Throwable t) {
@ -773,7 +783,7 @@ public class DailyNotificationWorker extends Worker {
long completionTime = System.currentTimeMillis(); long completionTime = System.currentTimeMillis();
// Store completion timestamp // 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); 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", return String.format("Active work: %d, Timestamps: %d",
activeWork.size(), workTimestamps.size()); activeWork.size(), workTimestamps.size());
} }
}

2
android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationContentDao.java

@ -219,7 +219,7 @@ public interface NotificationContentDao {
/** /**
* Get notification count by delivery status * 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<NotificationCountByStatus> getNotificationCountByDeliveryStatus(); List<NotificationCountByStatus> getNotificationCountByDeliveryStatus();
/** /**

2
android/plugin/src/main/java/com/timesafari/dailynotification/dao/NotificationDeliveryDao.java

@ -179,7 +179,7 @@ public interface NotificationDeliveryDao {
/** /**
* Get most common error codes * 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<ErrorCodeCount> getErrorCodeCounts(); List<ErrorCodeCount> getErrorCodeCounts();
/** /**

2
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.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.Index; import androidx.room.Index;
import androidx.room.Ignore;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
/** /**
@ -91,6 +92,7 @@ public class NotificationConfigEntity {
/** /**
* Constructor for configuration entries * Constructor for configuration entries
*/ */
@Ignore
public NotificationConfigEntity(@NonNull String id, String timesafariDid, public NotificationConfigEntity(@NonNull String id, String timesafariDid,
String configType, String configKey, String configType, String configKey,
String configValue, String configDataType) { String configValue, String configDataType) {

2
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.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.Index; import androidx.room.Index;
import androidx.room.Ignore;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
/** /**
@ -124,6 +125,7 @@ public class NotificationContentEntity {
/** /**
* Constructor with required fields * Constructor with required fields
*/ */
@Ignore
public NotificationContentEntity(@NonNull String id, String pluginVersion, String timesafariDid, public NotificationContentEntity(@NonNull String id, String pluginVersion, String timesafariDid,
String notificationType, String title, String body, String notificationType, String title, String body,
long scheduledTime, String timezone) { long scheduledTime, String timezone) {

2
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.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.room.Index; import androidx.room.Index;
import androidx.room.Ignore;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
/** /**
@ -124,6 +125,7 @@ public class NotificationDeliveryEntity {
/** /**
* Constructor for delivery tracking * Constructor for delivery tracking
*/ */
@Ignore
public NotificationDeliveryEntity(@NonNull String id, String notificationId, public NotificationDeliveryEntity(@NonNull String id, String notificationId,
String timesafariDid, String deliveryStatus, String timesafariDid, String deliveryStatus,
String deliveryMethod) { String deliveryMethod) {

Loading…
Cancel
Save