diff --git a/doc/daily-notification-plugin-android-receiver-issue.md b/doc/daily-notification-plugin-android-receiver-issue.md new file mode 100644 index 00000000..b051351b --- /dev/null +++ b/doc/daily-notification-plugin-android-receiver-issue.md @@ -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 + + + + + +``` + +- ✅ `exported="true"` is set (required for AlarmManager broadcasts) +- ✅ Intent action matches: `com.timesafari.daily.NOTIFICATION` +- ✅ Receiver is inside `` 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. diff --git a/scripts/check-alarm-logs.sh b/scripts/check-alarm-logs.sh new file mode 100755 index 00000000..d14a0636 --- /dev/null +++ b/scripts/check-alarm-logs.sh @@ -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" diff --git a/scripts/test-notification-receiver.sh b/scripts/test-notification-receiver.sh new file mode 100755 index 00000000..596d4439 --- /dev/null +++ b/scripts/test-notification-receiver.sh @@ -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'"