- Update Android manifest, Java imports, and capacitor.plugins.json to use org.timesafari.dailynotification (receivers, intent action, plugin classpath) - Update iOS Info.plist BGTaskSchedulerPermittedIdentifiers to org.timesafari.* - Bump @timesafari/daily-notification-plugin 1.3.3 → 2.0.0 (package-lock, Podfile.lock) - Update docs and test-notification-receiver.sh to reference new package/action names - Lockfile: minor bumps for @expo/cli, @expo/fingerprint, @expo/router-server, babel-preset-expo
252 lines
8.8 KiB
Markdown
252 lines
8.8 KiB
Markdown
# Daily Notification Plugin - Android Receiver Not Triggered by AlarmManager
|
|
|
|
**Date**: 2026-02-02
|
|
**Status**: ✅ Resolved (2026-02-06)
|
|
**Plugin**: @timesafari/daily-notification-plugin
|
|
**Platform**: Android
|
|
**Issue**: AlarmManager fires alarms but DailyNotificationReceiver is not receiving broadcasts
|
|
|
|
---
|
|
|
|
## Resolution (2026-02-06)
|
|
|
|
The bug was fixed in the plugin repository. The plugin now:
|
|
|
|
- Creates the PendingIntent with the receiver component explicitly set (`setComponent(ComponentName(context, DailyNotificationReceiver::class.java))`), so AlarmManager delivers the broadcast to the receiver.
|
|
- Adds the schedule ID to the Intent extras (`intent.putExtra("id", scheduleId)`), resolving the `missing_id` error.
|
|
|
|
**In this app after pulling the fix:**
|
|
|
|
1. Run `npm install` to get the latest plugin from `#master`.
|
|
2. Run `npx cap sync` so the Android (and iOS) native projects get the updated plugin code.
|
|
3. Run `node scripts/restore-local-plugins.js` if you use local plugins (e.g. SafeArea, SharedImage).
|
|
4. Rebuild and run on Android, then verify using the [Testing Steps for Plugin Fix](#testing-steps-for-plugin-fix) below.
|
|
</think>
|
|
|
|
---
|
|
|
|
## Problem Summary
|
|
|
|
Alarms are being scheduled successfully and fire at the correct time, but the `DailyNotificationReceiver` is not being triggered when AlarmManager delivers the broadcast. Manual broadcasts to the receiver work correctly, indicating the receiver itself is functional.
|
|
|
|
---
|
|
|
|
## What Works ✅
|
|
|
|
1. **Receiver Registration**: The receiver is properly registered in AndroidManifest.xml with `exported="true"`
|
|
2. **Manual Broadcasts**: Manually triggering the receiver via `adb shell am broadcast` successfully triggers it
|
|
3. **Alarm Scheduling**: Alarms are successfully scheduled via `setAlarmClock()` and appear in `dumpsys alarm`
|
|
4. **Alarm Firing**: Alarms fire at the scheduled time (confirmed by alarm disappearing from dumpsys)
|
|
|
|
---
|
|
|
|
## What Doesn't Work ❌
|
|
|
|
1. **Automatic Receiver Triggering**: When AlarmManager fires the alarm, the broadcast PendingIntent does not reach the receiver
|
|
2. **No Logs on Alarm Fire**: No `DN|RECEIVE_START` logs appear when alarms fire automatically
|
|
3. **Missing ID in Intent**: When manually tested, receiver shows `DN|RECEIVE_ERR missing_id` (separate issue but related)
|
|
|
|
---
|
|
|
|
## Technical Details
|
|
|
|
### Receiver Configuration
|
|
|
|
**File**: `android/app/src/main/AndroidManifest.xml`
|
|
|
|
```xml
|
|
<receiver
|
|
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
|
|
android:enabled="true"
|
|
android:exported="true">
|
|
<intent-filter>
|
|
<action android:name="org.timesafari.daily.NOTIFICATION" />
|
|
</intent-filter>
|
|
</receiver>
|
|
```
|
|
|
|
- ✅ `exported="true"` is set (required for AlarmManager broadcasts)
|
|
- ✅ Intent action matches: `org.timesafari.daily.NOTIFICATION`
|
|
- ✅ Receiver is inside `<application>` tag
|
|
|
|
### Alarm Scheduling Evidence
|
|
|
|
From logs when scheduling (23:51:32):
|
|
```
|
|
I DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=org.timesafari.daily.NOTIFICATION, triggerTime=1770105300000, requestCode=44490, scheduleId=timesafari_daily_reminder
|
|
I DNP-NOTIFY: Alarm clock scheduled (setAlarmClock): triggerAt=1770105300000, requestCode=44490
|
|
```
|
|
|
|
From `dumpsys alarm` output:
|
|
```
|
|
RTC_WAKEUP #36: Alarm{7a8fb5e type 0 origWhen 1770148800000 whenElapsed 122488536 app.timesafari.app}
|
|
tag=*walarm*:org.timesafari.daily.NOTIFICATION
|
|
type=RTC_WAKEUP origWhen=2026-02-03 12:00:00.000 window=0 exactAllowReason=policy_permission
|
|
operation=PendingIntent{6fce955: PendingIntentRecord{5856f6a app.timesafari.app broadcastIntent}}
|
|
```
|
|
|
|
### Alarm Firing Evidence
|
|
|
|
- Alarm scheduled for 23:55:00 (timestamp: 1770105300000)
|
|
- At 23:55:00, alarm is no longer in `dumpsys alarm` (confirmed it fired)
|
|
- **No `DN|RECEIVE_START` log at 23:55:00** (receiver was not triggered)
|
|
|
|
### Manual Broadcast Test (Works)
|
|
|
|
```bash
|
|
adb shell am broadcast -a org.timesafari.daily.NOTIFICATION -n app.timesafari.app/org.timesafari.dailynotification.DailyNotificationReceiver
|
|
```
|
|
|
|
**Result**: ✅ Receiver triggered successfully
|
|
```
|
|
02-02 23:46:07.505 DailyNotificationReceiver D DN|RECEIVE_START action=org.timesafari.daily.NOTIFICATION
|
|
02-02 23:46:07.506 DailyNotificationReceiver W DN|RECEIVE_ERR missing_id
|
|
```
|
|
|
|
---
|
|
|
|
## Root Cause Analysis
|
|
|
|
The issue appears to be in how the PendingIntent is created when scheduling alarms. Possible causes:
|
|
|
|
### Hypothesis 1: PendingIntent Not Targeting Receiver Correctly
|
|
|
|
The PendingIntent may be created without explicitly specifying the component, causing Android to not match it to the receiver when the alarm fires.
|
|
|
|
**Expected Fix**: When creating the PendingIntent for AlarmManager, explicitly set the component:
|
|
|
|
```kotlin
|
|
val intent = Intent("org.timesafari.daily.NOTIFICATION").apply {
|
|
setComponent(ComponentName(context, DailyNotificationReceiver::class.java))
|
|
putExtra("id", scheduleId) // Also fix missing_id issue
|
|
}
|
|
val pendingIntent = PendingIntent.getBroadcast(
|
|
context,
|
|
requestCode,
|
|
intent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
)
|
|
```
|
|
|
|
### Hypothesis 2: PendingIntent Flags Issue
|
|
|
|
The PendingIntent may be created with incorrect flags that prevent delivery when the app is in certain states.
|
|
|
|
**Check**: Ensure flags include:
|
|
- `FLAG_UPDATE_CURRENT` or `FLAG_CANCEL_CURRENT`
|
|
- `FLAG_IMMUTABLE` (required on Android 12+)
|
|
|
|
### Hypothesis 3: Package/Component Mismatch
|
|
|
|
The PendingIntent may be created with a different package name or component than what's registered in the manifest.
|
|
|
|
**Check**: Verify the package name in the Intent matches `app.timesafari.app` and the component matches the receiver class.
|
|
|
|
---
|
|
|
|
## Additional Issue: Missing ID in Intent
|
|
|
|
When the receiver IS triggered (manually), it shows:
|
|
```
|
|
DN|RECEIVE_ERR missing_id
|
|
```
|
|
|
|
This indicates the Intent extras don't include the `scheduleId`. The plugin should add the ID to the Intent when creating the PendingIntent:
|
|
|
|
```kotlin
|
|
intent.putExtra("id", scheduleId)
|
|
// or
|
|
intent.putExtra("scheduleId", scheduleId) // if receiver expects different key
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Steps for Plugin Fix
|
|
|
|
1. **Verify PendingIntent Creation**:
|
|
- Check the code that creates PendingIntent for AlarmManager
|
|
- Ensure component is explicitly set
|
|
- Ensure ID is added to Intent extras
|
|
|
|
2. **Test Alarm Delivery**:
|
|
- Schedule an alarm for 1-2 minutes in the future
|
|
- Monitor logs: `adb logcat | grep -E "DN|RECEIVE_START|DailyNotification"`
|
|
- Verify `DN|RECEIVE_START` appears when alarm fires
|
|
- Verify no `missing_id` error
|
|
|
|
3. **Test Different App States**:
|
|
- App in foreground
|
|
- App in background
|
|
- App force-closed
|
|
- Device in doze mode (if possible on emulator)
|
|
|
|
4. **Compare with Manual Broadcast**:
|
|
- Manual broadcast works → receiver is fine
|
|
- Alarm broadcast doesn't work → PendingIntent creation is the issue
|
|
|
|
---
|
|
|
|
## Files to Check in Plugin
|
|
|
|
1. **Alarm Scheduling Code**: Where `setAlarmClock()` or `setExact()` is called
|
|
2. **PendingIntent Creation**: Where `PendingIntent.getBroadcast()` is called
|
|
3. **Intent Creation**: Where the Intent for the alarm is created
|
|
4. **Receiver Code**: Verify what Intent extras it expects (for missing_id fix)
|
|
|
|
---
|
|
|
|
## Related Configuration
|
|
|
|
### AndroidManifest.xml (App Side)
|
|
- ✅ Receiver exported="true"
|
|
- ✅ Correct intent action
|
|
- ✅ Receiver inside application tag
|
|
|
|
### Permissions (App Side)
|
|
- ✅ POST_NOTIFICATIONS
|
|
- ✅ SCHEDULE_EXACT_ALARM
|
|
- ✅ RECEIVE_BOOT_COMPLETED
|
|
- ✅ WAKE_LOCK
|
|
- ❌ USE_EXACT_ALARM -- must not use; see note below
|
|
|
|
> **Note on `USE_EXACT_ALARM`:** The `USE_EXACT_ALARM` permission is restricted
|
|
> by Google on Android. Apps that declare it must be primarily dedicated to alarm
|
|
> or calendar functionality. Google will reject apps from the Play Store that use
|
|
> this permission for other purposes. This plugin uses `SCHEDULE_EXACT_ALARM`
|
|
> instead, which is sufficient for scheduling daily notifications.
|
|
|
|
---
|
|
|
|
## Expected Behavior After Fix
|
|
|
|
When an alarm fires:
|
|
1. AlarmManager delivers the broadcast
|
|
2. `DailyNotificationReceiver.onReceive()` is called
|
|
3. Log shows: `DN|RECEIVE_START action=org.timesafari.daily.NOTIFICATION`
|
|
4. Receiver finds the ID in Intent extras (no `missing_id` error)
|
|
5. Notification is displayed
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
- The `exported="true"` change in the app's manifest was necessary and correct
|
|
- The issue is in the plugin's PendingIntent creation, not the app configuration
|
|
- Manual broadcasts work, proving the receiver registration is correct
|
|
- Alarms fire, proving AlarmManager scheduling is correct
|
|
- The gap is in the PendingIntent → Receiver delivery
|
|
|
|
---
|
|
|
|
## Quick Reference: Working Manual Test
|
|
|
|
```bash
|
|
# This works - receiver is triggered
|
|
adb shell am broadcast \
|
|
-a org.timesafari.daily.NOTIFICATION \
|
|
-n app.timesafari.app/org.timesafari.dailynotification.DailyNotificationReceiver \
|
|
--es "id" "timesafari_daily_reminder"
|
|
```
|
|
|
|
The plugin's PendingIntent should create an equivalent broadcast that AlarmManager can deliver.
|