From 367325452ac0f62a4cd656ad6af502dc93fff47d Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 3 Feb 2026 00:33:33 -0800 Subject: [PATCH] fix(android): explicitly set component and package for AlarmManager broadcasts AlarmManager was firing alarms but DailyNotificationReceiver was not receiving broadcasts. The issue was that Intents created with Intent(context, Class) constructor were not reliably matched by AlarmManager when delivering broadcasts. Solution: Explicitly set ComponentName and package on all Intents used for AlarmManager broadcasts. This ensures AlarmManager can correctly match PendingIntents to the registered receiver. Changes: - NotifyReceiver.kt: Fixed Intent creation in scheduleNotification(), cancelNotification(), isAlarmScheduled(), and idempotence checks - ReactivationManager.kt: Fixed alarmsExist() to use DailyNotificationReceiver with explicit component/package - DailyNotificationScheduler.java: Fixed Intent creation to explicitly set component and package This fixes the critical bug where alarms fire but receivers are not triggered, resolving the gap between AlarmManager delivery and receiver execution. --- .../DailyNotificationScheduler.java | 13 ++++- .../dailynotification/NotifyReceiver.kt | 58 ++++++++++++++----- .../dailynotification/ReactivationManager.kt | 11 +++- 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationScheduler.java b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationScheduler.java index af7f2a4..c6b8507 100644 --- a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationScheduler.java +++ b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationScheduler.java @@ -12,6 +12,7 @@ package com.timesafari.dailynotification; import android.app.AlarmManager; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Build; @@ -154,9 +155,15 @@ public class DailyNotificationScheduler { cancelNotification(duplicateId); } - // Create intent for the notification - Intent intent = new Intent(context, DailyNotificationReceiver.class); - intent.setAction(com.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION); + // CRITICAL FIX: Explicitly set component and package for AlarmManager broadcasts + // AlarmManager requires explicit component matching when delivering broadcasts + ComponentName receiverComponent = new ComponentName( + context.getPackageName(), + "com.timesafari.dailynotification.DailyNotificationReceiver" + ); + Intent intent = new Intent(com.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION); + intent.setComponent(receiverComponent); + intent.setPackage(context.getPackageName()); intent.putExtra(com.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId()); // Check if this is a static reminder diff --git a/android/src/main/java/com/timesafari/dailynotification/NotifyReceiver.kt b/android/src/main/java/com/timesafari/dailynotification/NotifyReceiver.kt index 740fb01..cce6fe2 100644 --- a/android/src/main/java/com/timesafari/dailynotification/NotifyReceiver.kt +++ b/android/src/main/java/com/timesafari/dailynotification/NotifyReceiver.kt @@ -6,6 +6,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.BroadcastReceiver +import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.Build @@ -146,8 +147,15 @@ class NotifyReceiver : BroadcastReceiver() { // This prevents duplicate alarms when multiple scheduling paths race // Strategy: Check both by scheduleId (stable) and by trigger time (catches different scheduleIds for same time) val requestCode = getRequestCode(stableScheduleId) - val checkIntent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply { - action = "com.timesafari.daily.NOTIFICATION" + // CRITICAL FIX: Explicitly set component and package for AlarmManager broadcasts + // AlarmManager requires explicit component matching when delivering broadcasts + val receiverComponent = ComponentName( + context.packageName, + "com.timesafari.dailynotification.DailyNotificationReceiver" + ) + val checkIntent = Intent("com.timesafari.daily.NOTIFICATION").apply { + setComponent(receiverComponent) + setPackage(context.packageName) } // Check 1: Same scheduleId (stable requestCode) - most reliable @@ -269,12 +277,21 @@ class NotifyReceiver : BroadcastReceiver() { Log.w(TAG, "Failed to store notification content in database, continuing with alarm scheduling", e) } - // FIX: Use DailyNotificationReceiver (registered in manifest) instead of NotifyReceiver - // FIX: Set action to match manifest registration - val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply { - action = "com.timesafari.daily.NOTIFICATION" // Must match manifest intent-filter action - putExtra("notification_id", notificationId) // DailyNotificationReceiver expects this extra - putExtra("schedule_id", stableScheduleId) // Add stable scheduleId for tracking + // CRITICAL FIX: Explicitly set component and package for AlarmManager broadcasts + // AlarmManager requires explicit component matching when delivering broadcasts. + // Using Intent(context, Class) constructor may not work reliably with AlarmManager + // on all Android versions, especially when the app is in certain states. + // Solution: Create Intent with action, then explicitly set component and package. + val intent = Intent("com.timesafari.daily.NOTIFICATION").apply { + // Explicitly set component to ensure AlarmManager can match it to the receiver + setComponent(receiverComponent) + // Explicitly set package to ensure it matches the app's package (not plugin's) + setPackage(context.packageName) + // Must match manifest intent-filter action + // DailyNotificationReceiver expects this extra + putExtra("notification_id", notificationId) + // Add stable scheduleId for tracking + putExtra("schedule_id", stableScheduleId) // Also preserve original extras for backward compatibility if needed putExtra("title", config.title) putExtra("body", config.body) @@ -282,7 +299,8 @@ class NotifyReceiver : BroadcastReceiver() { putExtra("vibration", config.vibration ?: true) putExtra("priority", config.priority ?: "normal") putExtra("is_static_reminder", isStaticReminder) - putExtra("trigger_time", triggerAtMillis) // Store trigger time for debugging + // Store trigger time for debugging + putExtra("trigger_time", triggerAtMillis) if (reminderId != null) { putExtra("reminder_id", reminderId) } @@ -407,9 +425,14 @@ class NotifyReceiver : BroadcastReceiver() { */ fun cancelNotification(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null) { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - // FIX: Use DailyNotificationReceiver to match what was scheduled - val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply { - action = "com.timesafari.daily.NOTIFICATION" + // CRITICAL FIX: Use same Intent format as scheduling (explicit component and package) + val receiverComponent = ComponentName( + context.packageName, + "com.timesafari.dailynotification.DailyNotificationReceiver" + ) + val intent = Intent("com.timesafari.daily.NOTIFICATION").apply { + setComponent(receiverComponent) + setPackage(context.packageName) } val requestCode = when { scheduleId != null -> getRequestCode(scheduleId) @@ -438,9 +461,14 @@ class NotifyReceiver : BroadcastReceiver() { * @return true if alarm is scheduled, false otherwise */ fun isAlarmScheduled(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null): Boolean { - // FIX: Use DailyNotificationReceiver to match what was scheduled - val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply { - action = "com.timesafari.daily.NOTIFICATION" + // CRITICAL FIX: Use same Intent format as scheduling (explicit component and package) + val receiverComponent = ComponentName( + context.packageName, + "com.timesafari.dailynotification.DailyNotificationReceiver" + ) + val intent = Intent("com.timesafari.daily.NOTIFICATION").apply { + setComponent(receiverComponent) + setPackage(context.packageName) } val requestCode = when { scheduleId != null -> getRequestCode(scheduleId) diff --git a/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt b/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt index f339704..eef0685 100644 --- a/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt +++ b/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt @@ -1,6 +1,7 @@ package com.timesafari.dailynotification import android.app.PendingIntent +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -442,8 +443,14 @@ class ReactivationManager(private val context: Context) { return try { // Check if any PendingIntent for our receiver exists // This is more reliable than nextAlarmClock - val intent = Intent(context, NotifyReceiver::class.java).apply { - action = "com.timesafari.daily.NOTIFICATION" + // CRITICAL FIX: Use DailyNotificationReceiver with explicit component/package + val receiverComponent = ComponentName( + context.packageName, + "com.timesafari.dailynotification.DailyNotificationReceiver" + ) + val intent = Intent("com.timesafari.daily.NOTIFICATION").apply { + setComponent(receiverComponent) + setPackage(context.packageName) } val pendingIntent = PendingIntent.getBroadcast( context,