docs: Add investigation documentation and test scripts for notification receiver issue
After changing DailyNotificationReceiver to exported="true", testing revealed
that while the receiver works when manually triggered, AlarmManager broadcasts
are not reaching it when alarms fire automatically. Alarms are scheduled and
fire correctly, but the PendingIntent broadcast does not trigger the receiver.
Added comprehensive documentation and diagnostic tools:
1. Documentation (doc/daily-notification-plugin-android-receiver-issue.md):
- Complete problem analysis with evidence from logs and dumpsys
- Root cause hypotheses focusing on PendingIntent creation in plugin
- Testing steps and expected behavior after fix
- Technical details for plugin maintainer reference
2. Test scripts:
- scripts/test-notification-receiver.sh: Manually trigger receiver to
verify it works and test with/without ID parameter
- scripts/check-alarm-logs.sh: Check logs and verify alarm scheduling
Findings:
- Receiver registration is correct (exported="true" works for manual tests)
- Alarms schedule and fire successfully (confirmed via dumpsys alarm)
- Issue is in plugin's PendingIntent creation - broadcasts don't reach receiver
- Additional issue: Intent extras missing scheduleId (causes "missing_id" error)
The exported="true" change was necessary and correct. The remaining issue
requires a fix in the plugin's PendingIntent creation code to explicitly
set the component and include the scheduleId in Intent extras.
This documentation is intended for use when working on the plugin project
to fix the PendingIntent delivery issue.
This commit is contained in:
228
doc/daily-notification-plugin-android-receiver-issue.md
Normal file
228
doc/daily-notification-plugin-android-receiver-issue.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# Daily Notification Plugin - Android Receiver Not Triggered by AlarmManager
|
||||
|
||||
**Date**: 2026-02-02
|
||||
**Status**: 🔴 Critical Bug - Alarms Fire But Receiver Not Triggered
|
||||
**Plugin**: @timesafari/daily-notification-plugin
|
||||
**Platform**: Android
|
||||
**Issue**: AlarmManager fires alarms but DailyNotificationReceiver is not receiving broadcasts
|
||||
|
||||
---
|
||||
|
||||
## 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="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.timesafari.daily.NOTIFICATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
```
|
||||
|
||||
- ✅ `exported="true"` is set (required for AlarmManager broadcasts)
|
||||
- ✅ Intent action matches: `com.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=com.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*:com.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 com.timesafari.daily.NOTIFICATION -n app.timesafari.app/com.timesafari.dailynotification.DailyNotificationReceiver
|
||||
```
|
||||
|
||||
**Result**: ✅ Receiver triggered successfully
|
||||
```
|
||||
02-02 23:46:07.505 DailyNotificationReceiver D DN|RECEIVE_START action=com.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("com.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
|
||||
- ✅ USE_EXACT_ALARM
|
||||
- ✅ RECEIVE_BOOT_COMPLETED
|
||||
- ✅ WAKE_LOCK
|
||||
|
||||
---
|
||||
|
||||
## 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=com.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 com.timesafari.daily.NOTIFICATION \
|
||||
-n app.timesafari.app/com.timesafari.dailynotification.DailyNotificationReceiver \
|
||||
--es "id" "timesafari_daily_reminder"
|
||||
```
|
||||
|
||||
The plugin's PendingIntent should create an equivalent broadcast that AlarmManager can deliver.
|
||||
70
scripts/check-alarm-logs.sh
Executable file
70
scripts/check-alarm-logs.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# check-alarm-logs.sh
|
||||
# Author: Matthew Raymer
|
||||
# Description: Check logs around a specific time to see if alarm fired
|
||||
|
||||
# Function to find adb command
|
||||
find_adb() {
|
||||
# Check if adb is in PATH
|
||||
if command -v adb >/dev/null 2>&1; then
|
||||
echo "adb"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for ANDROID_HOME
|
||||
if [ -n "$ANDROID_HOME" ] && [ -x "$ANDROID_HOME/platform-tools/adb" ]; then
|
||||
echo "$ANDROID_HOME/platform-tools/adb"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for local.properties
|
||||
local local_props="android/local.properties"
|
||||
if [ -f "$local_props" ]; then
|
||||
local sdk_dir=$(grep "^sdk.dir=" "$local_props" 2>/dev/null | cut -d'=' -f2 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed "s|^file://||")
|
||||
if [ -n "$sdk_dir" ] && [ -x "$sdk_dir/platform-tools/adb" ]; then
|
||||
echo "$sdk_dir/platform-tools/adb"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try common macOS locations
|
||||
local common_locations=(
|
||||
"$HOME/Library/Android/sdk"
|
||||
"$HOME/Android/Sdk"
|
||||
"$HOME/.android/sdk"
|
||||
)
|
||||
|
||||
for sdk_path in "${common_locations[@]}"; do
|
||||
if [ -x "$sdk_path/platform-tools/adb" ]; then
|
||||
echo "$sdk_path/platform-tools/adb"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Not found
|
||||
return 1
|
||||
}
|
||||
|
||||
# Find adb
|
||||
ADB_CMD=$(find_adb)
|
||||
if [ $? -ne 0 ] || [ -z "$ADB_CMD" ]; then
|
||||
echo "Error: adb command not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Checking logs for alarm activity..."
|
||||
echo "Looking for: DN|RECEIVE_START, AlarmManager, DailyNotification, timesafari"
|
||||
echo ""
|
||||
|
||||
# Check recent logs for alarm-related activity
|
||||
echo "=== Recent alarm/receiver logs ==="
|
||||
"$ADB_CMD" logcat -d | grep -iE "DN|RECEIVE_START|RECEIVE_ERR|alarm.*timesafari|daily.*notification|com\.timesafari\.daily" | tail -20
|
||||
|
||||
echo ""
|
||||
echo "=== All AlarmManager activity (last 50 lines) ==="
|
||||
"$ADB_CMD" logcat -d | grep -i "AlarmManager" | tail -50
|
||||
|
||||
echo ""
|
||||
echo "=== Check if alarm is still scheduled ==="
|
||||
echo "Run this to see all scheduled alarms:"
|
||||
echo " $ADB_CMD shell dumpsys alarm | grep -A 5 timesafari"
|
||||
104
scripts/test-notification-receiver.sh
Executable file
104
scripts/test-notification-receiver.sh
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
# test-notification-receiver.sh
|
||||
# Author: Matthew Raymer
|
||||
# Description: Test script to manually trigger the DailyNotificationReceiver
|
||||
# to verify it's working correctly
|
||||
|
||||
# Function to find adb command
|
||||
find_adb() {
|
||||
# Check if adb is in PATH
|
||||
if command -v adb >/dev/null 2>&1; then
|
||||
echo "adb"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for ANDROID_HOME
|
||||
if [ -n "$ANDROID_HOME" ] && [ -x "$ANDROID_HOME/platform-tools/adb" ]; then
|
||||
echo "$ANDROID_HOME/platform-tools/adb"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for local.properties
|
||||
local local_props="android/local.properties"
|
||||
if [ -f "$local_props" ]; then
|
||||
local sdk_dir=$(grep "^sdk.dir=" "$local_props" 2>/dev/null | cut -d'=' -f2 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed "s|^file://||")
|
||||
if [ -n "$sdk_dir" ] && [ -x "$sdk_dir/platform-tools/adb" ]; then
|
||||
echo "$sdk_dir/platform-tools/adb"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try common macOS locations
|
||||
local common_locations=(
|
||||
"$HOME/Library/Android/sdk"
|
||||
"$HOME/Android/Sdk"
|
||||
"$HOME/.android/sdk"
|
||||
)
|
||||
|
||||
for sdk_path in "${common_locations[@]}"; do
|
||||
if [ -x "$sdk_path/platform-tools/adb" ]; then
|
||||
echo "$sdk_path/platform-tools/adb"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Not found
|
||||
return 1
|
||||
}
|
||||
|
||||
# Find adb
|
||||
ADB_CMD=$(find_adb)
|
||||
if [ $? -ne 0 ] || [ -z "$ADB_CMD" ]; then
|
||||
echo "Error: adb command not found!"
|
||||
echo ""
|
||||
echo "Please ensure one of the following:"
|
||||
echo " 1. adb is in your PATH"
|
||||
echo " 2. ANDROID_HOME is set and points to Android SDK"
|
||||
echo " 3. Android SDK is installed at:"
|
||||
echo " - $HOME/Library/Android/sdk (macOS default)"
|
||||
echo " - $HOME/Android/Sdk"
|
||||
echo ""
|
||||
echo "You can find your SDK location in Android Studio:"
|
||||
echo " Preferences > Appearance & Behavior > System Settings > Android SDK"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing DailyNotificationReceiver..."
|
||||
echo "Using adb: $ADB_CMD"
|
||||
echo ""
|
||||
|
||||
# Get the package name
|
||||
PACKAGE_NAME="app.timesafari.app"
|
||||
INTENT_ACTION="com.timesafari.daily.NOTIFICATION"
|
||||
|
||||
echo "Package: $PACKAGE_NAME"
|
||||
echo "Intent Action: $INTENT_ACTION"
|
||||
echo ""
|
||||
|
||||
# Check if device is connected
|
||||
if ! "$ADB_CMD" devices | grep -q $'\tdevice'; then
|
||||
echo "Error: No Android device/emulator connected!"
|
||||
echo ""
|
||||
echo "Please:"
|
||||
echo " 1. Start an Android emulator in Android Studio, or"
|
||||
echo " 2. Connect a physical device via USB"
|
||||
echo ""
|
||||
echo "Then run: $ADB_CMD devices"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 1: Send broadcast intent to trigger receiver (without ID - simulates current bug)
|
||||
echo "Test 1: Sending broadcast intent to DailyNotificationReceiver (without ID)..."
|
||||
"$ADB_CMD" shell am broadcast -a "$INTENT_ACTION" -n "$PACKAGE_NAME/com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||
|
||||
echo ""
|
||||
echo "Test 2: Sending broadcast intent WITH ID (to test if receiver works with ID)..."
|
||||
"$ADB_CMD" shell am broadcast -a "$INTENT_ACTION" -n "$PACKAGE_NAME/com.timesafari.dailynotification.DailyNotificationReceiver" --es "id" "timesafari_daily_reminder"
|
||||
|
||||
echo ""
|
||||
echo "Check logcat for 'DN|RECEIVE_START' to see if receiver was triggered"
|
||||
echo "Test 1 should show 'missing_id' error"
|
||||
echo "Test 2 should work correctly (if plugin supports it)"
|
||||
echo ""
|
||||
echo "To monitor logs, run:"
|
||||
echo " $ADB_CMD logcat | grep -E 'DN|RECEIVE_START|DailyNotification'"
|
||||
Reference in New Issue
Block a user