docs: add comprehensive integration guides and diagnostic method documentation
Add integration guides and update API documentation with new Android diagnostic methods. Emphasize critical NotifyReceiver registration requirement that was causing notification delivery failures. Documentation Updates: - API.md: Document isAlarmScheduled(), getNextAlarmTime(), testAlarm() - README.md: Add Quick Integration section and Android diagnostic methods - notification-testing-procedures.md: Add BroadcastReceiver troubleshooting New Integration Guides: - QUICK_INTEGRATION.md: Step-by-step guide for human developers - AI_INTEGRATION_GUIDE.md: Machine-readable guide with verification steps - TODO.md: Task tracking for pending improvements Key Improvements: - Explicit NotifyReceiver registration requirement highlighted - Complete troubleshooting flow for BroadcastReceiver issues - Diagnostic method examples for debugging alarm scheduling - AI-friendly integration instructions with verification commands Fixes notification delivery issues caused by missing NotifyReceiver registration in host app AndroidManifest.xml files.
This commit is contained in:
529
AI_INTEGRATION_GUIDE.md
Normal file
529
AI_INTEGRATION_GUIDE.md
Normal file
@@ -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 `<manifest>` tag, before `<application>` tag
|
||||
|
||||
**Before**:
|
||||
```xml
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
<!-- existing content -->
|
||||
</application>
|
||||
</manifest>
|
||||
```
|
||||
|
||||
**After**:
|
||||
```xml
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Required permissions -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application>
|
||||
<!-- existing content -->
|
||||
</application>
|
||||
</manifest>
|
||||
```
|
||||
|
||||
### Action: Add Receivers (CRITICAL)
|
||||
|
||||
**Location**: Inside `<application>` tag
|
||||
|
||||
**Before**:
|
||||
```xml
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<!-- existing activity config -->
|
||||
</activity>
|
||||
</application>
|
||||
```
|
||||
|
||||
**After**:
|
||||
```xml
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<!-- existing activity config -->
|
||||
</activity>
|
||||
|
||||
<!-- Daily Notification Plugin Receivers -->
|
||||
<!-- CRITICAL: NotifyReceiver is REQUIRED for notifications to work -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
|
||||
<!-- BootReceiver for reboot recovery (optional but recommended) -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
```
|
||||
|
||||
### Verification
|
||||
|
||||
```bash
|
||||
# Check if receivers are in manifest
|
||||
grep -A 3 "NotifyReceiver" android/app/src/main/AndroidManifest.xml
|
||||
|
||||
# Expected output:
|
||||
# <receiver
|
||||
# android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
# android:enabled="true"
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **Error**: "Notifications scheduled but not appearing"
|
||||
- **Check**: Verify `NotifyReceiver` is in manifest (see verification above)
|
||||
- **Solution**: Add the receiver if missing, rebuild app
|
||||
|
||||
- **Error**: "Permission denied"
|
||||
- **Check**: Verify permissions are in manifest
|
||||
- **Solution**: Add missing permissions, rebuild app
|
||||
|
||||
## Step 4: iOS Configuration
|
||||
|
||||
### File Path
|
||||
`ios/App/App/Info.plist`
|
||||
|
||||
### Action: Add Background Modes
|
||||
|
||||
**Location**: Inside root `<dict>` tag
|
||||
|
||||
**Before**:
|
||||
```xml
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<!-- other keys -->
|
||||
</dict>
|
||||
```
|
||||
|
||||
**After**:
|
||||
```xml
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<!-- other keys -->
|
||||
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>background-app-refresh</string>
|
||||
<string>background-processing</string>
|
||||
</array>
|
||||
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.content-fetch</string>
|
||||
<string>com.timesafari.dailynotification.notification-delivery</string>
|
||||
</array>
|
||||
</dict>
|
||||
```
|
||||
|
||||
### 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:
|
||||
# <key>UIBackgroundModes</key>
|
||||
# <array>
|
||||
# <string>background-app-refresh</string>
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
56
API.md
56
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<void>`
|
||||
|
||||
260
QUICK_INTEGRATION.md
Normal file
260
QUICK_INTEGRATION.md
Normal file
@@ -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
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Required permissions -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application>
|
||||
<!-- ... your existing application components ... -->
|
||||
|
||||
<!-- Daily Notification Plugin Receivers -->
|
||||
<!-- REQUIRED: NotifyReceiver for AlarmManager-based notifications -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
|
||||
<!-- BootReceiver for reboot recovery (optional but recommended) -->
|
||||
<receiver
|
||||
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
```
|
||||
|
||||
### 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
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>background-app-refresh</string>
|
||||
<string>background-processing</string>
|
||||
</array>
|
||||
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.timesafari.dailynotification.content-fetch</string>
|
||||
<string>com.timesafari.dailynotification.notification-delivery</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
59
README.md
59
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
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
@@ -487,9 +538,13 @@ await DailyNotification.updateDailyReminder('morning_checkin', {
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<!-- NotifyReceiver for AlarmManager-based notifications -->
|
||||
<!-- REQUIRED: Without this, alarms fire but notifications won't display -->
|
||||
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="com.timesafari.dailynotification.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
@@ -499,6 +554,8 @@ await DailyNotification.updateDailyReminder('morning_checkin', {
|
||||
</receiver>
|
||||
```
|
||||
|
||||
**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
|
||||
|
||||
210
TODO.md
Normal file
210
TODO.md
Normal file
@@ -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
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
#### 4. App Crashes on Force Stop
|
||||
// Get next alarm time
|
||||
const nextAlarm = await DailyNotification.getNextAlarmTime();
|
||||
|
||||
// Test alarm delivery
|
||||
await DailyNotification.testAlarm({ secondsFromNow: 10 });
|
||||
```
|
||||
|
||||
#### 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
|
||||
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
```
|
||||
- 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user