diff --git a/AI_INTEGRATION_GUIDE.md b/AI_INTEGRATION_GUIDE.md new file mode 100644 index 0000000..a9f889d --- /dev/null +++ b/AI_INTEGRATION_GUIDE.md @@ -0,0 +1,529 @@ +# Daily Notification Plugin - AI Integration Guide + +**Author**: Matthew Raymer +**Version**: 2.2.0 +**Last Updated**: 2025-11-06 +**Purpose**: Step-by-step guide optimized for AI agents to integrate this plugin + +## Overview + +This guide provides **explicit, unambiguous instructions** for integrating the Daily Notification Plugin into a Capacitor application. Each step includes: +- Exact file paths +- Before/after code examples +- Verification commands +- Expected outputs +- Error handling + +## Integration Checklist + +```yaml +steps: + - name: "Install plugin" + file: "package.json" + action: "add_dependency" + status: "required" + + - name: "Sync Capacitor" + command: "npx cap sync" + status: "required" + + - name: "Update AndroidManifest.xml" + file: "android/app/src/main/AndroidManifest.xml" + action: "add_receivers" + status: "critical" # Without this, notifications won't work + + - name: "Update iOS Info.plist" + file: "ios/App/App/Info.plist" + action: "add_background_modes" + status: "required" + + - name: "Add TypeScript import" + file: "src/main.ts" # or equivalent entry point + action: "import_plugin" + status: "required" +``` + +## Step 1: Install Plugin + +### Action +Add dependency to `package.json`: + +```json +{ + "dependencies": { + "@timesafari/daily-notification-plugin": "^1.0.1" + } +} +``` + +### Command +```bash +npm install @timesafari/daily-notification-plugin +``` + +### Verification +```bash +# Check if package is installed +npm list @timesafari/daily-notification-plugin + +# Expected output: +# └── @timesafari/daily-notification-plugin@1.0.1 +``` + +### Error Handling +- **Error**: "Package not found" + - **Solution**: Check npm registry access or use Git URL: `npm install git+https://github.com/timesafari/daily-notification-plugin.git` + +## Step 2: Sync Capacitor + +### Command +```bash +npx cap sync android +npx cap sync ios +``` + +### Verification +```bash +# Check if plugin is in capacitor.plugins.json +cat android/app/src/main/assets/capacitor.plugins.json | grep DailyNotification + +# Expected output should include: +# "DailyNotification": { "class": "com.timesafari.dailynotification.DailyNotificationPlugin" } +``` + +### Error Handling +- **Error**: "Plugin not found in capacitor.plugins.json" + - **Solution**: Run `npx cap sync` again, ensure plugin is in `node_modules` + +## Step 3: Android Configuration + +### File Path +`android/app/src/main/AndroidManifest.xml` + +### Action: Add Permissions + +**Location**: Inside `` tag, before `` tag + +**Before**: +```xml + + + + + +``` + +**After**: +```xml + + + + + + + + + + + + +``` + +### Action: Add Receivers (CRITICAL) + +**Location**: Inside `` tag + +**Before**: +```xml + + + + + +``` + +**After**: +```xml + + + + + + + + + + + + + + + + + +``` + +### Verification + +```bash +# Check if receivers are in manifest +grep -A 3 "NotifyReceiver" android/app/src/main/AndroidManifest.xml + +# Expected output: +# ` tag + +**Before**: +```xml + + CFBundleName + App + + +``` + +**After**: +```xml + + CFBundleName + App + + + UIBackgroundModes + + background-app-refresh + background-processing + + + BGTaskSchedulerPermittedIdentifiers + + com.timesafari.dailynotification.content-fetch + com.timesafari.dailynotification.notification-delivery + + +``` + +### Action: Enable Capabilities (Manual Step) + +**Note**: This requires Xcode UI interaction, cannot be automated + +1. Open `ios/App/App.xcworkspace` in Xcode +2. Select app target +3. Go to "Signing & Capabilities" tab +4. Click "+ Capability" +5. Add "Background Modes" +6. Check "Background App Refresh" and "Background Processing" + +### Verification + +```bash +# Check if background modes are in Info.plist +grep -A 3 "UIBackgroundModes" ios/App/App/Info.plist + +# Expected output: +# UIBackgroundModes +# +# background-app-refresh +``` + +## Step 5: TypeScript Integration + +### File Path +`src/main.ts` (or your app's entry point) + +### Action: Import Plugin + +**Before**: +```typescript +import { createApp } from 'vue' +import App from './App.vue' + +const app = createApp(App) +app.mount('#app') +``` + +**After**: +```typescript +import { createApp } from 'vue' +import App from './App.vue' +import '@capacitor/core' +import '@timesafari/daily-notification-plugin' + +const app = createApp(App) +app.mount('#app') +``` + +### Action: Use Plugin + +**File**: Any component or service file + +```typescript +import { DailyNotification } from '@timesafari/daily-notification-plugin'; + +// Configure plugin +await DailyNotification.configure({ + storage: 'tiered', + ttlSeconds: 1800, + enableETagSupport: true +}); + +// Request permissions +const status = await DailyNotification.checkPermissions(); +if (status.notifications !== 'granted') { + await DailyNotification.requestPermissions(); +} + +// Schedule notification +await DailyNotification.scheduleDailyReminder({ + id: 'test', + title: 'Test Notification', + body: 'This is a test', + time: '09:00', + sound: true, + vibration: true, + priority: 'normal' +}); +``` + +### Verification + +```typescript +// Check if plugin is available +if (window.Capacitor?.Plugins?.DailyNotification) { + console.log('✅ Plugin registered'); +} else { + console.error('❌ Plugin not found'); +} +``` + +## Step 6: Build and Test + +### Build Commands + +```bash +# Android +cd android +./gradlew assembleDebug + +# iOS +cd ios +pod install +# Then build in Xcode +``` + +### Test Commands + +```bash +# Install on Android device +adb install app/build/outputs/apk/debug/app-debug.apk + +# Check logs +adb logcat | grep -E "DNP-|NotifyReceiver|DailyNotification" +``` + +### Expected Log Output (Success) + +``` +DNP-PLUGIN: DailyNotification plugin initialized +DNP-NOTIFY: Alarm clock scheduled (setAlarmClock): triggerAt=... +DNP-NOTIFY: Notification receiver triggered: triggerTime=... +``` + +### Error Log Patterns + +``` +# Missing NotifyReceiver +# No logs from "Notification receiver triggered" + +# Missing permissions +# Error: "Permission denied" or "SCHEDULE_EXACT_ALARM not granted" + +# Plugin not registered +# Error: "Cannot read property 'DailyNotification' of undefined" +``` + +## Complete Integration Example + +### File Structure +``` +my-capacitor-app/ +├── package.json # Step 1: Add dependency +├── src/ +│ └── main.ts # Step 5: Import plugin +├── android/ +│ └── app/ +│ └── src/ +│ └── main/ +│ └── AndroidManifest.xml # Step 3: Add receivers +└── ios/ + └── App/ + └── App/ + └── Info.plist # Step 4: Add background modes +``` + +### Complete Code Example + +**`src/services/notification-service.ts`**: +```typescript +import { DailyNotification } from '@timesafari/daily-notification-plugin'; + +export class NotificationService { + async initialize() { + // Configure plugin + await DailyNotification.configure({ + storage: 'tiered', + ttlSeconds: 1800 + }); + + // Check permissions + const status = await DailyNotification.checkPermissions(); + if (status.notifications !== 'granted') { + await DailyNotification.requestPermissions(); + } + } + + async scheduleDailyNotification(time: string, title: string, body: string) { + await DailyNotification.scheduleDailyReminder({ + id: `daily_${Date.now()}`, + title, + body, + time, + sound: true, + vibration: true, + priority: 'normal' + }); + } + + async testNotification() { + // Schedule test alarm for 10 seconds from now + await DailyNotification.testAlarm({ secondsFromNow: 10 }); + } +} +``` + +## Verification Checklist + +Run these checks to verify integration: + +```bash +# 1. Plugin installed +npm list @timesafari/daily-notification-plugin + +# 2. Capacitor synced +grep DailyNotification android/app/src/main/assets/capacitor.plugins.json + +# 3. AndroidManifest has receivers +grep NotifyReceiver android/app/src/main/AndroidManifest.xml + +# 4. iOS Info.plist has background modes +grep UIBackgroundModes ios/App/App/Info.plist + +# 5. TypeScript can import +# (Check for no TypeScript errors in your IDE) +``` + +## Troubleshooting Decision Tree + +``` +Notifications not appearing? +├─ Is NotifyReceiver in AndroidManifest.xml? +│ ├─ No → Add receiver, rebuild +│ └─ Yes → Continue +├─ Are permissions granted? +│ ├─ No → Request permissions +│ └─ Yes → Continue +├─ Is alarm scheduled? +│ ├─ Check: await DailyNotification.isAlarmScheduled({...}) +│ ├─ No → Schedule alarm +│ └─ Yes → Continue +└─ Check logs: adb logcat | grep DNP- + ├─ No logs → Plugin not initialized + └─ Logs present → Check for errors +``` + +## AI Agent Instructions + +For AI agents integrating this plugin: + +1. **Parse this guide sequentially** - Each step builds on the previous +2. **Verify each step** - Use verification commands before proceeding +3. **Handle errors explicitly** - Check error handling section for each step +4. **Test after integration** - Use test commands to verify functionality +5. **Check logs** - Use ADB logs to diagnose issues + +### Key Files to Modify + +```yaml +files: + - path: "package.json" + action: "add_dependency" + key: "dependencies.@timesafari/daily-notification-plugin" + + - path: "android/app/src/main/AndroidManifest.xml" + action: "add_xml_elements" + elements: + - type: "uses-permission" + name: "android.permission.POST_NOTIFICATIONS" + - type: "receiver" + name: "com.timesafari.dailynotification.NotifyReceiver" + attributes: + android:enabled: "true" + android:exported: "false" + + - path: "ios/App/App/Info.plist" + action: "add_dict_keys" + keys: + - "UIBackgroundModes" + - "BGTaskSchedulerPermittedIdentifiers" + + - path: "src/main.ts" # or entry point + action: "add_import" + import: "@timesafari/daily-notification-plugin" +``` + +## Success Criteria + +Integration is successful when: + +1. ✅ Plugin installs without errors +2. ✅ `capacitor.plugins.json` contains DailyNotification entry +3. ✅ AndroidManifest.xml contains NotifyReceiver +4. ✅ iOS Info.plist contains background modes +5. ✅ TypeScript imports work without errors +6. ✅ `window.Capacitor.Plugins.DailyNotification` is available +7. ✅ Test alarm fires successfully (use `testAlarm()`) + +## Next Steps + +After successful integration: +- Read [API.md](./API.md) for complete API reference +- Check [README.md](./README.md) for advanced usage +- Review [docs/notification-testing-procedures.md](./docs/notification-testing-procedures.md) for testing + diff --git a/API.md b/API.md index b25bea8..124fd11 100644 --- a/API.md +++ b/API.md @@ -2,7 +2,7 @@ **Author**: Matthew Raymer **Version**: 2.2.0 -**Last Updated**: 2025-10-08 06:02:45 UTC +**Last Updated**: 2025-11-06 09:51:00 UTC ## Overview @@ -74,6 +74,60 @@ Open exact alarm settings in system preferences. Get reboot recovery status and statistics. +##### `isAlarmScheduled(options: { triggerAtMillis: number }): Promise<{ scheduled: boolean; triggerAtMillis: number }>` + +Check if an alarm is scheduled for a specific trigger time. Useful for debugging and verification. + +**Parameters:** +- `options.triggerAtMillis`: `number` - The trigger time in milliseconds (Unix timestamp) + +**Returns:** +- `scheduled`: `boolean` - Whether the alarm is currently scheduled +- `triggerAtMillis`: `number` - The trigger time that was checked + +**Example:** +```typescript +const result = await DailyNotification.isAlarmScheduled({ + triggerAtMillis: 1762421400000 +}); +console.log(`Alarm scheduled: ${result.scheduled}`); +``` + +##### `getNextAlarmTime(): Promise<{ scheduled: boolean; triggerAtMillis?: number }>` + +Get the next scheduled alarm time from AlarmManager. Requires Android 5.0+ (API 21+). + +**Returns:** +- `scheduled`: `boolean` - Whether any alarm is scheduled +- `triggerAtMillis`: `number | undefined` - The next alarm trigger time (if scheduled) + +**Example:** +```typescript +const result = await DailyNotification.getNextAlarmTime(); +if (result.scheduled) { + const nextAlarm = new Date(result.triggerAtMillis); + console.log(`Next alarm: ${nextAlarm.toLocaleString()}`); +} +``` + +##### `testAlarm(options?: { secondsFromNow?: number }): Promise<{ scheduled: boolean; secondsFromNow: number; triggerAtMillis: number }>` + +Schedule a test alarm that fires in a few seconds. Useful for verifying alarm delivery works correctly. + +**Parameters:** +- `options.secondsFromNow`: `number` (optional) - Seconds from now to fire the alarm (default: 5) + +**Returns:** +- `scheduled`: `boolean` - Whether the alarm was scheduled successfully +- `secondsFromNow`: `number` - The delay used +- `triggerAtMillis`: `number` - The trigger time in milliseconds + +**Example:** +```typescript +const result = await DailyNotification.testAlarm({ secondsFromNow: 10 }); +console.log(`Test alarm scheduled for ${result.secondsFromNow} seconds`); +``` + ### Management Methods #### `maintainRollingWindow(): Promise` diff --git a/QUICK_INTEGRATION.md b/QUICK_INTEGRATION.md new file mode 100644 index 0000000..df5da07 --- /dev/null +++ b/QUICK_INTEGRATION.md @@ -0,0 +1,260 @@ +# Daily Notification Plugin - Quick Integration Guide + +**Author**: Matthew Raymer +**Version**: 2.2.0 +**Last Updated**: 2025-11-06 + +## Overview + +This guide provides a **quick, step-by-step** process for integrating the Daily Notification Plugin into any Capacitor application. For detailed documentation, see [README.md](./README.md) and [API.md](./API.md). + +**For AI Agents**: See [AI_INTEGRATION_GUIDE.md](./AI_INTEGRATION_GUIDE.md) for explicit, machine-readable integration instructions with verification steps and error handling. + +## Prerequisites + +- Capacitor 6.0+ project +- Android Studio (for Android development) +- Xcode 14+ (for iOS development) +- Node.js 18+ + +## Step 1: Install the Plugin + +```bash +npm install @timesafari/daily-notification-plugin +``` + +Or install from Git: + +```bash +npm install git+https://github.com/timesafari/daily-notification-plugin.git +``` + +## Step 2: Sync Capacitor + +```bash +npx cap sync android +npx cap sync ios +``` + +## Step 3: Android Configuration + +### 3.1 Update AndroidManifest.xml + +**⚠️ CRITICAL**: You **must** add the `NotifyReceiver` registration to your app's `AndroidManifest.xml`. Without it, alarms will fire but notifications won't be displayed. + +Add to `android/app/src/main/AndroidManifest.xml`: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### 3.2 Update build.gradle (if needed) + +The plugin should work with standard Capacitor setup. If you encounter dependency issues, ensure these are in `android/app/build.gradle`: + +```gradle +dependencies { + // ... your existing dependencies ... + + // Plugin dependencies (usually auto-added by Capacitor sync) + implementation "androidx.room:room-runtime:2.6.1" + implementation "androidx.work:work-runtime-ktx:2.9.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" + annotationProcessor "androidx.room:room-compiler:2.6.1" +} +``` + +## Step 4: iOS Configuration + +### 4.1 Update Info.plist + +Add to `ios/App/App/Info.plist`: + +```xml +UIBackgroundModes + + background-app-refresh + background-processing + + +BGTaskSchedulerPermittedIdentifiers + + com.timesafari.dailynotification.content-fetch + com.timesafari.dailynotification.notification-delivery + +``` + +### 4.2 Enable Capabilities + +In Xcode: +1. Select your app target +2. Go to "Signing & Capabilities" +3. Enable "Background Modes" +4. Check "Background App Refresh" and "Background Processing" + +## Step 5: Use the Plugin + +### Basic Usage + +```typescript +import { DailyNotification } from '@timesafari/daily-notification-plugin'; + +// Configure the plugin +await DailyNotification.configure({ + storage: 'tiered', + ttlSeconds: 1800, + enableETagSupport: true +}); + +// Schedule a daily notification +await DailyNotification.scheduleDailyNotification({ + title: 'Daily Update', + body: 'Your daily content is ready', + schedule: '0 9 * * *' // 9 AM daily (cron format) +}); +``` + +### Request Permissions + +```typescript +// Check permissions +const status = await DailyNotification.checkPermissions(); +console.log('Notification permission:', status.notifications); + +// Request permissions +if (status.notifications !== 'granted') { + await DailyNotification.requestPermissions(); +} +``` + +### Schedule a Simple Reminder + +```typescript +// Schedule a static daily reminder (no network required) +await DailyNotification.scheduleDailyReminder({ + id: 'morning_checkin', + title: 'Good Morning!', + body: 'Time to check your updates', + time: '09:00', // HH:mm format + sound: true, + vibration: true, + priority: 'normal' +}); +``` + +### Diagnostic Methods (Android) + +```typescript +// Check if an alarm is scheduled +const result = await DailyNotification.isAlarmScheduled({ + triggerAtMillis: scheduledTime +}); +console.log('Alarm scheduled:', result.scheduled); + +// Get next alarm time +const nextAlarm = await DailyNotification.getNextAlarmTime(); +if (nextAlarm.scheduled) { + console.log('Next alarm:', new Date(nextAlarm.triggerAtMillis)); +} + +// Test alarm delivery (schedules alarm for 10 seconds from now) +await DailyNotification.testAlarm({ secondsFromNow: 10 }); +``` + +## Step 6: Verify Installation + +### Check Plugin Registration + +```typescript +// Verify plugin is available +if (window.Capacitor?.Plugins?.DailyNotification) { + console.log('✅ Plugin is registered'); +} else { + console.error('❌ Plugin not found'); +} +``` + +### Test Notification + +```typescript +// Schedule a test notification for 10 seconds from now +await DailyNotification.testAlarm({ secondsFromNow: 10 }); + +// Or schedule a regular notification +await DailyNotification.scheduleDailyReminder({ + id: 'test', + title: 'Test Notification', + body: 'This is a test', + time: new Date(Date.now() + 60000).toTimeString().slice(0, 5) // 1 minute from now +}); +``` + +## Troubleshooting + +### Notifications Not Appearing + +1. **Check NotifyReceiver Registration**: Verify `NotifyReceiver` is in your `AndroidManifest.xml` (see Step 3.1) +2. **Check Permissions**: Ensure notification permissions are granted +3. **Check Logs**: Use ADB to check logs: + ```bash + adb logcat | grep -E "DNP-|NotifyReceiver|Notification" + ``` +4. **Use Diagnostic Methods**: Use `isAlarmScheduled()` and `getNextAlarmTime()` to verify alarms + +### Common Issues + +#### Android: "Alarm fires but notification doesn't appear" +- **Solution**: Ensure `NotifyReceiver` is registered in your app's `AndroidManifest.xml` (not just the plugin's manifest) + +#### Android: "Permission denied" errors +- **Solution**: Request `POST_NOTIFICATIONS` and `SCHEDULE_EXACT_ALARM` permissions + +#### iOS: Background tasks not running +- **Solution**: Ensure Background Modes are enabled in Xcode capabilities + +#### Plugin not found +- **Solution**: Run `npx cap sync` and rebuild the app + +## Next Steps + +- Read the [API Reference](./API.md) for complete method documentation +- Check [README.md](./README.md) for advanced usage examples +- Review [docs/notification-testing-procedures.md](./docs/notification-testing-procedures.md) for testing guidance + +## Support + +For issues or questions: +- Check the troubleshooting section above +- Review the [API documentation](./API.md) +- Check [docs/notification-testing-procedures.md](./docs/notification-testing-procedures.md) for debugging steps + diff --git a/README.md b/README.md index ec7e5f9..d7b3359 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,19 @@ npm install git+https://github.com/timesafari/daily-notification-plugin.git The plugin follows the standard Capacitor Android structure - no additional path configuration needed! +## Quick Integration + +**New to the plugin?** Start with the [Quick Integration Guide](./QUICK_INTEGRATION.md) for step-by-step setup instructions. + +The quick guide covers: +- Installation and setup +- AndroidManifest.xml configuration (⚠️ **Critical**: NotifyReceiver registration) +- iOS configuration +- Basic usage examples +- Troubleshooting common issues + +**For AI Agents**: See [AI Integration Guide](./AI_INTEGRATION_GUIDE.md) for explicit, machine-readable instructions with verification steps, error handling, and decision trees. + ## Quick Start ### Basic Usage @@ -312,6 +325,42 @@ const status = await DailyNotification.getDualScheduleStatus(); // } ``` +### Android Diagnostic Methods + +#### `isAlarmScheduled(options)` + +Check if an alarm is scheduled for a specific trigger time. Useful for debugging and verification. + +```typescript +const result = await DailyNotification.isAlarmScheduled({ + triggerAtMillis: 1762421400000 // Unix timestamp in milliseconds +}); +console.log(`Alarm scheduled: ${result.scheduled}`); +``` + +#### `getNextAlarmTime()` + +Get the next scheduled alarm time from AlarmManager. Requires Android 5.0+ (API 21+). + +```typescript +const result = await DailyNotification.getNextAlarmTime(); +if (result.scheduled) { + const nextAlarm = new Date(result.triggerAtMillis); + console.log(`Next alarm: ${nextAlarm.toLocaleString()}`); +} +``` + +#### `testAlarm(options?)` + +Schedule a test alarm that fires in a few seconds. Useful for verifying alarm delivery works correctly. + +```typescript +// Schedule test alarm for 10 seconds from now +const result = await DailyNotification.testAlarm({ secondsFromNow: 10 }); +console.log(`Test alarm scheduled for ${result.secondsFromNow} seconds`); +console.log(`Will fire at: ${new Date(result.triggerAtMillis).toLocaleString()}`); +``` + ## Capacitor Compatibility Matrix | Plugin Version | Capacitor Version | Status | Notes | @@ -480,6 +529,8 @@ await DailyNotification.updateDailyReminder('morning_checkin', { #### AndroidManifest.xml +**⚠️ CRITICAL**: The `NotifyReceiver` registration is **required** for alarm-based notifications to work. Without it, alarms will fire but notifications won't be displayed. + ```xml @@ -487,9 +538,13 @@ await DailyNotification.updateDailyReminder('morning_checkin', { + + + android:exported="false"> + + @@ -499,6 +554,8 @@ await DailyNotification.updateDailyReminder('morning_checkin', { ``` +**Note**: The `NotifyReceiver` must be registered in your app's `AndroidManifest.xml`, not just in the plugin's manifest. If notifications aren't appearing even though alarms are scheduled, check that `NotifyReceiver` is properly registered. + #### build.gradle ```gradle diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..c9bcb7d --- /dev/null +++ b/TODO.md @@ -0,0 +1,210 @@ +# Daily Notification Plugin - TODO Items + +**Last Updated**: 2025-11-06 +**Status**: Active tracking of pending improvements and features + +--- + +## 🔴 High Priority + +### 1. Add Instrumentation Tests +**Status**: In Progress +**Priority**: High +**Context**: Expand beyond basic `ExampleInstrumentedTest.java` + +**Tasks**: +- [x] Create comprehensive instrumentation test suite +- [x] Test alarm scheduling and delivery +- [x] Test BroadcastReceiver registration +- [x] Test alarm status checking +- [x] Test alarm cancellation +- [x] Test unique request codes +- [ ] Test notification display (requires UI testing) +- [ ] Test prefetch mechanism (requires WorkManager testing) +- [ ] Test permission handling edge cases +- [ ] Test offline scenarios + +**Location**: `test-apps/daily-notification-test/android/app/src/androidTest/java/com/timesafari/dailynotification/NotificationInstrumentationTest.java` + +**Reference**: `docs/android-app-improvement-plan.md` - Phase 2: Testing & Reliability + +**Completed**: Created `NotificationInstrumentationTest.java` with tests for: +- NotifyReceiver registration verification +- Alarm scheduling with setAlarmClock() +- Unique request code generation +- Alarm status checking (isAlarmScheduled) +- Next alarm time retrieval +- Alarm cancellation +- PendingIntent uniqueness + +--- + +### 2. Update Documentation +**Status**: ✅ Completed +**Priority**: High +**Context**: Documentation needs updates for recent changes + +**Tasks**: +- [x] Update API reference with new methods (`isAlarmScheduled`, `getNextAlarmTime`, `testAlarm`) +- [x] Document NotifyReceiver registration requirements +- [x] Update AndroidManifest.xml examples +- [x] Document alarm scheduling improvements (`setAlarmClock()`) +- [x] Add troubleshooting guide for BroadcastReceiver issues +- [ ] Update integration guide with Vue test app setup + +**Completed**: Updated documentation in: +- `API.md`: Added new diagnostic methods with examples +- `README.md`: Added Android diagnostic methods section, emphasized NotifyReceiver requirement +- `docs/notification-testing-procedures.md`: Added troubleshooting for BroadcastReceiver issues, diagnostic method usage + +**Reference**: `docs/android-app-improvement-plan.md` - Phase 3: Security & Performance + +--- + +## 🟡 Medium Priority + +### 3. Phase 2 Platform Implementation +**Status**: Pending +**Priority**: Medium +**Context**: Complete platform-specific implementations per specification + +**Android Tasks**: +- [ ] WorkManager integration improvements +- [ ] SQLite storage implementation (shared database) +- [ ] TTL enforcement at notification fire time +- [ ] Rolling window safety mechanisms +- [ ] ETag support for content fetching + +**iOS Tasks**: +- [ ] BGTaskScheduler implementation +- [ ] UNUserNotificationCenter integration +- [ ] Background task execution +- [ ] T–lead prefetch logic + +**Storage System**: +- [ ] SQLite schema design with TTL rules +- [ ] WAL (Write-Ahead Logging) mode +- [ ] Shared database access pattern +- [ ] Hot-read verification for UI + +**Callback Registry**: +- [ ] Full implementation with retries +- [ ] Redaction support for sensitive data +- [ ] Webhook delivery mechanism +- [ ] Error handling and recovery + +**Reference**: `doc/implementation-roadmap.md` - Phase 2 details + +--- + +### 4. Performance Optimization +**Status**: Pending +**Priority**: Medium +**Context**: Optimize battery usage and system resources + +**Tasks**: +- [ ] Battery optimization recommendations +- [ ] Network request optimization +- [ ] Background execution efficiency +- [ ] Memory usage optimization +- [ ] CPU usage profiling + +**Reference**: `code-summary-for-chatgpt.md` - Production Readiness Checklist + +--- + +### 5. Security Audit +**Status**: Pending +**Priority**: Medium +**Context**: Security hardening review + +**Tasks**: +- [ ] Permission validation review +- [ ] Input sanitization audit +- [ ] Network security review +- [ ] Storage encryption review +- [ ] JWT token handling security + +**Reference**: `code-summary-for-chatgpt.md` - Production Readiness Checklist + +--- + +## 🟢 Low Priority / Nice-to-Have + +### 6. iOS Implementation Completion +**Status**: Pending +**Priority**: Low +**Context**: Complete iOS platform implementation + +**Tasks**: +- [ ] BGTaskScheduler registration +- [ ] Background task handlers +- [ ] UNUserNotificationCenter integration +- [ ] UserDefaults storage improvements +- [ ] Background App Refresh handling + +**Reference**: `code-summary-for-chatgpt.md` - Production Readiness Checklist + +--- + +### 7. Monitoring and Analytics +**Status**: Pending +**Priority**: Low +**Context**: Add observability and metrics + +**Tasks**: +- [ ] Structured logging improvements +- [ ] Health monitoring endpoints +- [ ] Success rate tracking +- [ ] Latency metrics +- [ ] Error distribution tracking + +**Reference**: `doc/directives/0001-Daily-Notification-Plugin-Implementation-Directive.md` + +--- + +### 8. User Documentation +**Status**: Pending +**Priority**: Low +**Context**: End-user documentation + +**Tasks**: +- [ ] User guide for notification setup +- [ ] Troubleshooting guide for users +- [ ] Battery optimization instructions +- [ ] Permission setup guide + +**Reference**: `code-summary-for-chatgpt.md` - Production Readiness Checklist + +--- + +### 9. Production Deployment Guide +**Status**: Pending +**Priority**: Low +**Context**: Deployment procedures + +**Tasks**: +- [ ] Production build configuration +- [ ] Release checklist +- [ ] Rollback procedures +- [ ] Monitoring setup guide + +**Reference**: `DEPLOYMENT_CHECKLIST.md` + +--- + +## 📝 Notes + +- **CI/CD**: Excluded from this list per project requirements +- **Current Focus**: High priority items (#1 and #2) +- **Recent Completion**: NotifyReceiver registration fix (2025-11-06) +- **Verification**: Notification system working in both test apps + +--- + +**Related Documents**: +- `docs/android-app-improvement-plan.md` - Detailed improvement plan +- `doc/implementation-roadmap.md` - Implementation phases +- `DEPLOYMENT_CHECKLIST.md` - Deployment procedures +- `test-apps/daily-notification-test/TODO_NATIVE_FETCHER.md` - Native fetcher TODOs + diff --git a/docs/notification-testing-procedures.md b/docs/notification-testing-procedures.md index 159b06e..fc881be 100644 --- a/docs/notification-testing-procedures.md +++ b/docs/notification-testing-procedures.md @@ -435,8 +435,36 @@ adb logcat -c - Check exact alarm permissions: `adb shell "dumpsys alarm | grep SCHEDULE_EXACT_ALARM"` - Verify alarm is scheduled: `adb shell "dumpsys alarm | grep timesafari"` - Check battery optimization settings +- Use diagnostic methods to verify alarm status: + ```typescript + // Check if alarm is scheduled + const status = await DailyNotification.isAlarmScheduled({ + triggerAtMillis: scheduledTime + }); + + // Get next alarm time + const nextAlarm = await DailyNotification.getNextAlarmTime(); + + // Test alarm delivery + await DailyNotification.testAlarm({ secondsFromNow: 10 }); + ``` -#### 4. App Crashes on Force Stop +#### 4. BroadcastReceiver Not Invoked +**Symptoms**: Alarm fires but notification doesn't appear, no logs from `NotifyReceiver` +**Solutions**: +- **CRITICAL**: Verify `NotifyReceiver` is registered in `AndroidManifest.xml`: + ```xml + + + ``` +- Check logs for `NotifyReceiver` registration: `adb logcat -d | grep -i "NotifyReceiver"` +- Verify the receiver is in your app's manifest, not just the plugin's manifest +- Check if app process is killed: `adb shell "ps | grep timesafari"` +- Review alarm scheduling logs: `adb logcat -d | grep -E "DNP-NOTIFY|Alarm clock"` + +#### 5. App Crashes on Force Stop **Symptoms**: App crashes when force-stopped **Solutions**: - This is expected behavior - force-stop kills the app diff --git a/test-apps/daily-notification-test/android/app/src/androidTest/java/com/timesafari/dailynotification/NotificationInstrumentationTest.java b/test-apps/daily-notification-test/android/app/src/androidTest/java/com/timesafari/dailynotification/NotificationInstrumentationTest.java new file mode 100644 index 0000000..9676c0e --- /dev/null +++ b/test-apps/daily-notification-test/android/app/src/androidTest/java/com/timesafari/dailynotification/NotificationInstrumentationTest.java @@ -0,0 +1,286 @@ +package com.timesafari.dailynotification; + +import static org.junit.Assert.*; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.GrantPermissionRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Calendar; + +/** + * Instrumentation tests for Daily Notification Plugin + * + * Tests critical notification scheduling and delivery paths + */ +@RunWith(AndroidJUnit4.class) +public class NotificationInstrumentationTest { + + private Context appContext; + private AlarmManager alarmManager; + + @Rule + public GrantPermissionRule permissionRule = GrantPermissionRule.grant( + android.Manifest.permission.POST_NOTIFICATIONS, + android.Manifest.permission.SCHEDULE_EXACT_ALARM + ); + + @Before + public void setUp() { + appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + alarmManager = (AlarmManager) appContext.getSystemService(Context.ALARM_SERVICE); + } + + @Test + public void testNotifyReceiverRegistration() { + // Verify NotifyReceiver is registered in AndroidManifest + Intent intent = new Intent(appContext, NotifyReceiver.class); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + appContext, + 0, + intent, + PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE + ); + + // If NotifyReceiver is registered, we can create a PendingIntent for it + assertNotNull("NotifyReceiver should be registered in AndroidManifest", pendingIntent); + } + + @Test + public void testAlarmScheduling() { + // Test that alarms can be scheduled + long triggerTime = System.currentTimeMillis() + 60000; // 1 minute from now + + Intent intent = new Intent(appContext, NotifyReceiver.class); + intent.putExtra("title", "Test Notification"); + intent.putExtra("body", "Test body"); + + int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + appContext, + requestCode, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); + + try { + // Use setAlarmClock for Android 5.0+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo( + triggerTime, + null + ); + alarmManager.setAlarmClock(alarmClockInfo, pendingIntent); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerTime, + pendingIntent + ); + } else { + alarmManager.setExact( + AlarmManager.RTC_WAKEUP, + triggerTime, + pendingIntent + ); + } + + // Verify alarm is scheduled + boolean isScheduled = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime); + assertTrue("Alarm should be scheduled", isScheduled); + + // Clean up + alarmManager.cancel(pendingIntent); + } catch (SecurityException e) { + fail("Should have permission to schedule exact alarms: " + e.getMessage()); + } + } + + @Test + public void testUniqueRequestCodes() { + // Test that different trigger times generate different request codes + long triggerTime1 = System.currentTimeMillis() + 60000; + long triggerTime2 = System.currentTimeMillis() + 120000; + + int requestCode1 = NotifyReceiver.Companion.getRequestCode(triggerTime1); + int requestCode2 = NotifyReceiver.Companion.getRequestCode(triggerTime2); + + // Request codes should be different for different trigger times + assertNotEquals("Different trigger times should generate different request codes", + requestCode1, requestCode2); + } + + @Test + public void testAlarmStatusCheck() { + // Test isAlarmScheduled method + long triggerTime = System.currentTimeMillis() + 60000; + + // Initially should not be scheduled + boolean initiallyScheduled = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime); + assertFalse("Alarm should not be scheduled initially", initiallyScheduled); + + // Schedule alarm + Intent intent = new Intent(appContext, NotifyReceiver.class); + intent.putExtra("title", "Test"); + intent.putExtra("body", "Test"); + + int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + appContext, + requestCode, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo( + triggerTime, + null + ); + alarmManager.setAlarmClock(alarmClockInfo, pendingIntent); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerTime, + pendingIntent + ); + } else { + alarmManager.setExact( + AlarmManager.RTC_WAKEUP, + triggerTime, + pendingIntent + ); + } + + // Now should be scheduled + boolean afterScheduling = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime); + assertTrue("Alarm should be scheduled after calling setAlarmClock", afterScheduling); + + // Clean up + alarmManager.cancel(pendingIntent); + } catch (SecurityException e) { + fail("Should have permission to schedule exact alarms: " + e.getMessage()); + } + } + + @Test + public void testNextAlarmTime() { + // Test getNextAlarmTime method (requires Android 5.0+) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Long nextAlarmTime = NotifyReceiver.Companion.getNextAlarmTime(appContext); + + // May be null if no alarm scheduled, which is valid + if (nextAlarmTime != null) { + assertTrue("Next alarm time should be in the future", + nextAlarmTime > System.currentTimeMillis()); + } + } + } + + @Test + public void testAlarmCancellation() { + // Test that alarms can be cancelled + long triggerTime = System.currentTimeMillis() + 60000; + + Intent intent = new Intent(appContext, NotifyReceiver.class); + intent.putExtra("title", "Test"); + intent.putExtra("body", "Test"); + + int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + appContext, + requestCode, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); + + try { + // Schedule alarm + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo( + triggerTime, + null + ); + alarmManager.setAlarmClock(alarmClockInfo, pendingIntent); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerTime, + pendingIntent + ); + } else { + alarmManager.setExact( + AlarmManager.RTC_WAKEUP, + triggerTime, + pendingIntent + ); + } + + // Verify scheduled + assertTrue("Alarm should be scheduled", + NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime)); + + // Cancel alarm + NotifyReceiver.Companion.cancelNotification(appContext, triggerTime); + + // Verify cancelled + assertFalse("Alarm should be cancelled", + NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime)); + } catch (SecurityException e) { + fail("Should have permission to schedule exact alarms: " + e.getMessage()); + } + } + + @Test + public void testPendingIntentUniqueness() { + // Test that PendingIntents with different request codes don't conflict + long triggerTime1 = System.currentTimeMillis() + 60000; + long triggerTime2 = System.currentTimeMillis() + 120000; + + Intent intent1 = new Intent(appContext, NotifyReceiver.class); + intent1.putExtra("title", "Test 1"); + intent1.putExtra("body", "Test 1"); + + Intent intent2 = new Intent(appContext, NotifyReceiver.class); + intent2.putExtra("title", "Test 2"); + intent2.putExtra("body", "Test 2"); + + int requestCode1 = NotifyReceiver.Companion.getRequestCode(triggerTime1); + int requestCode2 = NotifyReceiver.Companion.getRequestCode(triggerTime2); + + PendingIntent pendingIntent1 = PendingIntent.getBroadcast( + appContext, + requestCode1, + intent1, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); + + PendingIntent pendingIntent2 = PendingIntent.getBroadcast( + appContext, + requestCode2, + intent2, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); + + // Both PendingIntents should be created successfully + assertNotNull("First PendingIntent should be created", pendingIntent1); + assertNotNull("Second PendingIntent should be created", pendingIntent2); + + // They should be different objects + assertNotSame("PendingIntents should be different objects", + pendingIntent1, pendingIntent2); + } +} +