Files
daily-notification-plugin/docs/explore-alarm-behavior-directive.md
Matthew Raymer 6aa9140f67 docs: add comprehensive alarm/notification behavior documentation
- Add platform capability reference (Android & iOS OS-level facts)
- Add plugin behavior exploration template (executable test matrices)
- Add plugin requirements & implementation directive
- Add Android-specific implementation directive with detailed test procedures
- Add exploration findings from code inspection
- Add improvement directive for refining documentation structure
- Add Android alarm persistence directive (OS capabilities)

All documents include:
- File locations, function references, and line numbers
- Detailed test procedures with ADB commands
- Cross-platform comparisons
- Implementation checklists and code examples
2025-11-21 07:30:25 +00:00

671 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DIRECTIVE: Explore & Document Alarm / Schedule / Notification Behavior in Capacitor Plugin (Android & iOS)
**Author**: Matthew Raymer
**Date**: November 2025
**Status**: Active Directive - Exploration Phase
## 0. Scope & Objective
We want to **explore, test, and document** how the *current* Capacitor plugin handles:
- **Alarms / schedules / reminders**
- **Local notifications**
- **Persistence and recovery** across:
- App kill / swipe from recents
- OS process kill
- Device reboot
- **Force stop** (Android) / hard termination (iOS)
- Cross-platform **semantic differences** between Android and iOS
The focus is **observation of current behavior**, not yet changing implementation.
We want a clear map of **what the plugin actually guarantees** on each platform.
---
## 1. Key Questions to Answer
For **each platform (Android, iOS)** and for **each "scheduled thing"** the plugin supports (alarms, reminders, scheduled notifications, repeating schedules, etc.):
### 1.1 How is it implemented under the hood?
- **Android**: AlarmManager? WorkManager? JobScheduler? Foreground service?
- **iOS**: UNUserNotificationCenter? BGTaskScheduler? background fetch? timers in foreground?
### 1.2 What happens when the app is:
- Swiped away from recents?
- Killed by OS (memory pressure)?
- Device rebooted?
- On Android: explicitly **Force Stopped** in system settings?
- On iOS: explicitly swiped away, then device rebooted before next trigger?
### 1.3 What is persisted?
Are schedules/alarms stored in:
- SQLite / Room / shared preferences (Android)?
- CoreData / UserDefaults / files (iOS)?
- Or are they only in RAM / native scheduler?
### 1.4 What is re-created and when?
- On boot?
- On app cold start?
- On notification tap?
- Not at all?
### 1.5 What does the plugin *promise* to the JS/TS layer?
- "Will always fire even after reboot"?
- "Will fire as long as app hasn't been force-stopped"?
- "Best-effort only"?
We are trying to align **plugin promises** with **real platform capabilities and limitations.**
---
## 2. Android Exploration
### 2.1 Code-Level Inspection
**Source Locations:**
- **Plugin Implementation**: `android/src/main/java/com/timesafari/dailynotification/` (Kotlin files may also be present)
- **Manifest**: `android/src/main/AndroidManifest.xml`
- **Test Applications**:
- `test-apps/android-test-app/` - Primary Android test app
- `test-apps/daily-notification-test/` - Additional test application
**Tasks:**
1. **Locate the Android implementation in the plugin:**
- Primary path: `android/src/main/java/com/timesafari/dailynotification/`
- Key files to examine:
- `DailyNotificationPlugin.kt` - Main plugin class (see `scheduleDailyNotification()` at line 1302)
- `DailyNotificationWorker.java` - WorkManager worker (see `doWork()` at line 59)
- `DailyNotificationReceiver.java` - BroadcastReceiver for alarms (see `onReceive()` at line 51)
- `NotifyReceiver.kt` - AlarmManager scheduling (see `scheduleExactNotification()` at line 92)
- `BootReceiver.kt` - Boot recovery (see `onReceive()` at line 24)
- `FetchWorker.kt` - WorkManager fetch scheduling (see `scheduleFetch()` at line 31)
2. **Identify the mechanisms used to schedule work:**
- **AlarmManager**: Used via `NotifyReceiver.scheduleExactNotification()` (line 92)
- `setAlarmClock()` for Android 5.0+ (line 219)
- `setExactAndAllowWhileIdle()` for Android 6.0+ (line 223)
- `setExact()` for older versions (line 231)
- **WorkManager**: Used for background fetching and notification processing
- `FetchWorker.scheduleFetch()` (line 31) - Uses `OneTimeWorkRequest`
- `DailyNotificationWorker.doWork()` (line 59) - Processes notifications
- **No JobScheduler**: Not used in current implementation
- **No repeating alarms**: Uses one-time alarms with rescheduling
3. **Inspect how notifications are issued:**
- **NotificationCompat**: Used in `DailyNotificationWorker.displayNotification()` and `NotifyReceiver.showNotification()` (line 482)
- **setFullScreenIntent**: Not currently used (check `NotifyReceiver.showNotification()` at line 443)
- **Notification channels**: Created in `NotifyReceiver.showNotification()` (lines 454-470)
- Channel ID: `"timesafari.daily"` (see `DailyNotificationWorker.java` line 46)
- Importance based on priority (HIGH/DEFAULT/LOW)
- **ChannelManager**: Check for separate channel management class
4. **Check for permissions & receivers:**
- Manifest: `android/src/main/AndroidManifest.xml`
- Look for:
- `RECEIVE_BOOT_COMPLETED` permission
- `SCHEDULE_EXACT_ALARM` permission
- Any `BroadcastReceiver` declarations for:
- `BOOT_COMPLETED` / `LOCKED_BOOT_COMPLETED`
- Custom alarm intent actions
- Check test app manifests:
- `test-apps/android-test-app/app/src/main/AndroidManifest.xml`
- `test-apps/daily-notification-test/` (if applicable)
5. **Determine persistence strategy:**
- Where are scheduled alarms stored?
- SharedPreferences?
- SQLite / Room?
- Not at all (just in AlarmManager/work queue)?
6. **Check for reschedule-on-boot or reschedule-on-app-launch logic:**
- **BootReceiver**: `android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt`
- `onReceive()` handles `ACTION_BOOT_COMPLETED` (line 24)
- `rescheduleNotifications()` reloads from database and reschedules (line 38+)
- Calls `NotifyReceiver.scheduleExactNotification()` for each schedule (line 74)
- **Alternative**: `DailyNotificationRebootRecoveryManager.java` has `BootCompletedReceiver` inner class (line 278)
- **App launch recovery**: Check `DailyNotificationPlugin.kt` for initialization logic that reschedules on app start
---
### 2.2 Behavior Testing Matrix (Android)
**Test Applications:**
- **Primary**: `test-apps/android-test-app/` - Use this for comprehensive testing
- **Secondary**: `test-apps/daily-notification-test/` - Additional test scenarios if needed
**Run these tests on a real device or emulator.**
For each scenario, record:
- Did the alarm / notification fire?
- Did it fire on time?
- From what state did the app wake up (cold, warm, already running)?
- Any visible logs / errors?
**Scenarios:**
#### 2.2.1 Base Case
- Schedule an alarm/notification 2 minutes in the future.
- Leave app in foreground or background.
- Confirm it fires.
#### 2.2.2 Swipe from Recents
- Schedule alarm (25 minutes).
- Swipe app away from recents.
- Wait for trigger time.
- Observe: does alarm still fire?
#### 2.2.3 OS Kill (simulate memory pressure)
- Mainly observational; may be tricky to force, but:
- Open many other apps or use `adb shell am kill <package>` (not force-stop).
- Confirm whether scheduled alarm still fires.
#### 2.2.4 Device Reboot
- Schedule alarm (e.g. 10 minutes in the future).
- Reboot device.
- Do **not** reopen app.
- Wait past scheduled time:
- Does plugin reschedule and fire automatically?
- Or does nothing happen until user opens the app?
Then:
- After device reboot, manually open the app.
- Does plugin detect missed alarms and:
- Fire "missed" behavior?
- Reschedule future alarms?
- Or silently forget them?
#### 2.2.5 Android Force Stop
- Schedule alarm.
- Go to Settings → Apps → [Your App] → Force stop.
- Wait for trigger time.
- Observe: it should **not** fire (OS-level rule).
- Then open app again and see if plugin automatically:
- Detects missed alarms and recovers, or
- Treats them as lost.
**Goal:** build a clear empirical table of plugin behavior vs Android's known rules.
---
## 3. iOS Exploration
### 3.1 Code-Level Inspection
**Source Locations:**
- **Plugin Implementation**: `ios/Plugin/` - Swift plugin files
- **Alternative Branch**: Check `ios-2` branch for additional iOS implementations or variations
- **Test Applications**:
- `test-apps/ios-test-app/` - Primary iOS test app
- `test-apps/daily-notification-test/` - Additional test application (if iOS support exists)
**Tasks:**
1. **Locate the iOS implementation:**
- Primary path: `ios/Plugin/`
- Key files to examine:
- `DailyNotificationPlugin.swift` - Main plugin class (see `scheduleUserNotification()` at line 506)
- `DailyNotificationScheduler.swift` - Notification scheduling (see `scheduleNotification()` at line 133)
- `DailyNotificationBackgroundTasks.swift` - BGTaskScheduler handlers
- **Also check**: `ios-2` branch for alternative implementations or newer iOS code
```bash
git checkout ios-2
# Compare ios/Plugin/DailyNotificationPlugin.swift
```
2. **Identify the scheduling mechanism:**
- **UNUserNotificationCenter**: Primary mechanism
- `DailyNotificationScheduler.scheduleNotification()` uses `UNCalendarNotificationTrigger` (line 172)
- `DailyNotificationPlugin.scheduleUserNotification()` uses `UNTimeIntervalNotificationTrigger` (line 514)
- No `UNLocationNotificationTrigger` found
- **BGTaskScheduler**: Used for background fetch
- `DailyNotificationPlugin.scheduleBackgroundFetch()` (line 495)
- Uses `BGAppRefreshTaskRequest` (line 496)
- **No timers**: No plain Timer usage found (would die with app)
3. **Determine what's persisted:**
- Does the plugin store alarms in:
- `UserDefaults`?
- Files / CoreData?
- Or only within UNUserNotificationCenter's pending notification requests (no parallel app-side storage)?
4. **Check for re-scheduling behavior on app launch:**
- On app start (cold or warm), does plugin:
- Query `UNUserNotificationCenter` for pending notifications?
- Compare against its own store?
- Attempt to rebuild schedules?
- Or does it rely solely on UNUserNotificationCenter to manage everything?
5. **Determine capabilities / limitations:**
- Can the plugin run arbitrary code *when the notification fires*?
- Only via notification actions / `didReceive response` callbacks.
- Does it support repeating notifications (daily/weekly)?
---
### 3.2 Behavior Testing Matrix (iOS)
**Test Applications:**
- **Primary**: `test-apps/ios-test-app/` - Use this for comprehensive testing
- **Secondary**: `test-apps/daily-notification-test/` - Additional test scenarios if needed
- **Note**: Compare behavior between main branch and `ios-2` branch implementations if they differ
As with Android, test:
#### 3.2.1 Base Case
- Schedule local notification 25 minutes in the future; leave app backgrounded.
- Confirm it fires on time with app in background.
#### 3.2.2 Swipe App Away
- Schedule notification, then swipe app away from app switcher.
- Confirm notification still fires (iOS local notification center should handle this).
#### 3.2.3 Device Reboot
- Schedule notification for a future time.
- Reboot device.
- Do **not** open app.
- Test whether:
- Notification still fires (iOS usually persists scheduled local notifications across reboot), or
- Behavior depends on trigger type (time vs calendar, etc.).
#### 3.2.4 Hard Termination & Relaunch
- Schedule some repeating notification(s).
- Terminate app via Xcode / app switcher.
- Allow some triggers to occur.
- Reopen app and inspect whether plugin:
- Notices anything about missed events, or
- Simply trusts that UNUserNotificationCenter handled user-visible parts.
**Goal:** map what your *plugin* adds on top of native behavior vs what is entirely delegated to the OS.
---
## 4. Cross-Platform Behavior & Promise Alignment
After exploration, produce a summary:
### 4.1 What the plugin actually guarantees to JS callers
- "Scheduled reminders will still fire after app swipe / kill"
- "On Android, reminders may not survive device reboot unless app is opened"
- "After Force Stop (Android), nothing runs until user opens app"
- "On iOS, local notifications themselves persist across reboot, but no extra app code runs at fire time unless user interacts"
### 4.2 Where semantics differ
- Android may require explicit rescheduling on boot; iOS may not.
- Android **force stop** is a hard wall; iOS has no exact equivalent in user-facing settings.
- Plugin may currently:
- Over-promise on reliability, or
- Under-document platform limitations.
### 4.3 Where we need to add warnings / notes in the public API
- E.g. "This schedule is best-effort; on Android, device reboot may cancel it unless you open the app again," etc.
---
## 5. Deliverables from This Exploration
### 5.1 Doc: `ALARMS_BEHAVIOR_MATRIX.md`
A table of scenarios (per platform) vs observed behavior.
Includes:
- App state
- OS event (reboot, force stop, etc.)
- What fired, what didn't
- Log snippets where useful
### 5.2 Doc: `PLUGIN_ALARM_LIMITATIONS.md`
Plain-language explanation of:
- Android hard limits (Force Stop, reboot behavior)
- iOS behavior (local notifications vs app code execution)
- Clear note on what the plugin promises.
### 5.3 Annotated code pointers
Commented locations in Android/iOS code where:
- Scheduling is performed
- Persistence (if any) is implemented
- Rescheduling (if any) is implemented
### 5.4 Open Questions / TODOs
Gaps uncovered:
- No reschedule-on-boot?
- No persistence of schedules?
- No handling of "missed" alarms on reactivation?
- Potential next-step directives (separate document) to improve behavior.
---
## 6. One-Liner Summary
> This directive is to **investigate, not change**: we want a precise, tested understanding of what our Capacitor plugin *currently* does with alarms/schedules/notifications on Android and iOS, especially across kills, reboots, and force stops, and where that behavior does or does not match what we think we're promising to app developers.
---
## Related Documentation
- [Android Alarm Persistence Directive](./android-alarm-persistence-directive.md) - General Android alarm capabilities and limitations
- [Boot Receiver Testing Guide](./boot-receiver-testing-guide.md) - Testing boot receiver behavior
- [App Startup Recovery Solution](./app-startup-recovery-solution.md) - Recovery mechanisms on app launch
- [Reboot Testing Procedure](./reboot-testing-procedure.md) - Step-by-step reboot testing
---
## Source Code Structure Reference
### Android Source Files
**Primary Plugin Code:**
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt` (or `.java`)
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java`
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java`
- `android/src/main/java/com/timesafari/dailynotification/ChannelManager.java`
- `android/src/main/AndroidManifest.xml`
**Test Applications:**
- `test-apps/android-test-app/app/src/main/` - Test app source
- `test-apps/android-test-app/app/src/main/assets/public/index.html` - Test UI
- `test-apps/daily-notification-test/` - Additional test app (if present)
### iOS Source Files
**Primary Plugin Code:**
- `ios/Plugin/DailyNotificationPlugin.swift` (or similar)
- `ios/Plugin/` - All Swift plugin files
- **Also check**: `ios-2` branch for alternative implementations
**Test Applications:**
- `test-apps/ios-test-app/` - Test app source
- `test-apps/daily-notification-test/` - Additional test app (if iOS support exists)
---
## Detailed Code References (File Locations, Functions, Line Numbers)
### Android Implementation Details
#### Alarm Scheduling
**File**: `android/src/main/java/com/timesafari/dailynotification/NotifyReceiver.kt`
- **Function**: `scheduleExactNotification()` - **Lines 92-247**
- Schedules exact alarms using AlarmManager
- Uses `setAlarmClock()` for Android 5.0+ (API 21+) - **Line 219**
- Falls back to `setExactAndAllowWhileIdle()` for Android 6.0+ (API 23+) - **Line 223**
- Falls back to `setExact()` for older versions - **Line 231**
- Called from:
- `DailyNotificationPlugin.kt` - `scheduleDailyNotification()` - **Line 1385**
- `DailyNotificationPlugin.kt` - `scheduleDailyReminder()` - **Line 809**
- `DailyNotificationPlugin.kt` - `scheduleDualNotification()` - **Line 1685**
- `BootReceiver.kt` - `rescheduleNotifications()` - **Line 74**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt`
- **Function**: `scheduleDailyNotification()` - **Lines 1302-1417**
- Main plugin method for scheduling notifications
- Checks exact alarm permission - **Line 1309**
- Opens settings if permission not granted - **Lines 1314-1324**
- Calls `NotifyReceiver.scheduleExactNotification()` - **Line 1385**
- Schedules prefetch 2 minutes before notification - **Line 1395**
- **Function**: `scheduleDailyReminder()` - **Lines 777-833**
- Schedules static reminders (no content dependency)
- Calls `NotifyReceiver.scheduleExactNotification()` - **Line 809**
- **Function**: `canScheduleExactAlarms()` - **Lines 835-860**
- Checks if exact alarm permission is granted (Android 12+)
**File**: `android/src/main/java/com/timesafari/dailynotification/PendingIntentManager.java`
- **Function**: `scheduleExactAlarm()` - **Lines 127-158**
- Uses `setExactAndAllowWhileIdle()` - **Line 135**
- Falls back to `setExact()` - **Line 141**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationExactAlarmManager.java`
- **Function**: `scheduleExactAlarm()` - **Lines 186-201**
- Uses `setExactAndAllowWhileIdle()` - **Line 189**
- Falls back to `setExact()` - **Line 193**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationScheduler.java`
- **Function**: `scheduleExactAlarm()` - **Lines 237-272**
- Uses `setExactAndAllowWhileIdle()` - **Line 242**
- Falls back to `setExact()` - **Line 251**
#### WorkManager Usage
**File**: `android/src/main/java/com/timesafari/dailynotification/FetchWorker.kt`
- **Function**: `scheduleFetch()` - **Lines 31-59**
- Schedules WorkManager one-time work request
- Uses `OneTimeWorkRequestBuilder` - **Line 36**
- Enqueues with `WorkManager.getInstance().enqueueUniqueWork()` - **Lines 53-58**
- **Function**: `scheduleDelayedPrefetch()` - **Lines 62-131**
- Schedules delayed prefetch work
- Uses `setInitialDelay()` - **Line 104**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java`
- **Function**: `doWork()` - **Lines 59-915**
- Main WorkManager worker execution
- Handles notification display - **Line 91**
- Calls `displayNotification()` - **Line 200+**
- **Function**: `displayNotification()` - **Lines 200-400+**
- Displays notification using NotificationCompat
- Ensures notification channel exists
- Uses `NotificationCompat.Builder` - **Line 200+**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetcher.java`
- **Function**: `scheduleFetch()` - **Lines 78-140**
- Schedules WorkManager fetch work
- Uses `OneTimeWorkRequest.Builder` - **Line 106**
- Enqueues with `workManager.enqueueUniqueWork()` - **Lines 115-119**
#### Boot Recovery
**File**: `android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt`
- **Class**: `BootReceiver` - **Lines 18-100+**
- BroadcastReceiver for BOOT_COMPLETED
- `onReceive()` - **Line 24** - Handles boot intent
- `rescheduleNotifications()` - **Line 38+** - Reschedules all notifications from database
- Calls `NotifyReceiver.scheduleExactNotification()` - **Line 74**
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationRebootRecoveryManager.java`
- **Class**: `BootCompletedReceiver` - **Lines 278-297**
- Inner BroadcastReceiver for boot events
- `onReceive()` - **Line 280** - Handles BOOT_COMPLETED action
- Calls `handleSystemReboot()` - **Line 290**
#### Notification Display
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java`
- **Function**: `onReceive()` - **Lines 51-485**
- Lightweight BroadcastReceiver triggered by AlarmManager
- Enqueues WorkManager work for heavy operations - **Line 100+**
- Extracts notification ID and action from intent
**File**: `android/src/main/java/com/timesafari/dailynotification/NotifyReceiver.kt`
- **Function**: `showNotification()` - **Lines 443-500**
- Displays notification using NotificationCompat
- Creates notification channel if needed - **Lines 454-470**
- Uses `NotificationCompat.Builder` - **Line 482**
#### Persistence
**File**: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt`
- Database operations use Room database:
- `getDatabase()` - Returns DailyNotificationDatabase instance
- Schedule storage in `scheduleDailyNotification()` - **Lines 1393-1410**
- Schedule storage in `scheduleDailyReminder()` - **Lines 813-821**
#### Permissions & Manifest
**File**: `android/src/main/AndroidManifest.xml`
- **Note**: Plugin manifest is minimal; receivers declared in consuming app manifest
- Check test app manifest: `test-apps/android-test-app/app/src/main/AndroidManifest.xml`
- Look for `RECEIVE_BOOT_COMPLETED` permission
- Look for `SCHEDULE_EXACT_ALARM` permission
- Look for `BootReceiver` registration
- Look for `DailyNotificationReceiver` registration
### iOS Implementation Details
#### Notification Scheduling
**File**: `ios/Plugin/DailyNotificationPlugin.swift`
- **Class**: `DailyNotificationPlugin` - **Lines 24-532**
- Main Capacitor plugin class
- Uses `UNUserNotificationCenter.current()` - **Line 26**
- Uses `BGTaskScheduler.shared` - **Line 27**
- **Function**: `scheduleUserNotification()` - **Lines 506-529**
- Schedules notification using UNUserNotificationCenter
- Creates `UNMutableNotificationContent` - **Line 507**
- Creates `UNTimeIntervalNotificationTrigger` - **Line 514**
- Adds request via `notificationCenter.add()` - **Line 522**
- **Function**: `scheduleBackgroundFetch()` - **Lines 495-504**
- Schedules BGTaskScheduler background fetch
- Creates `BGAppRefreshTaskRequest` - **Line 496**
- Submits via `backgroundTaskScheduler.submit()` - **Line 502**
**File**: `ios/Plugin/DailyNotificationScheduler.swift`
- **Class**: `DailyNotificationScheduler` - **Lines 20-236+**
- Manages UNUserNotificationCenter scheduling
- **Function**: `scheduleNotification()` - **Lines 133-198**
- Schedules notification with calendar trigger
- Creates `UNCalendarNotificationTrigger` - **Lines 172-175**
- Creates `UNNotificationRequest` - **Lines 178-182**
- Adds via `notificationCenter.add()` - **Line 185**
- **Function**: `cancelNotification()` - **Lines 205-213**
- Cancels notification by ID
- Uses `notificationCenter.removePendingNotificationRequests()` - **Line 206**
#### Background Tasks
**File**: `ios/Plugin/DailyNotificationBackgroundTasks.swift`
- Background task handling for BGTaskScheduler
- Register background task identifiers
- Handle background fetch execution
#### Persistence
**File**: `ios/Plugin/DailyNotificationPlugin.swift**
- **Note**: Check for UserDefaults, CoreData, or file-based storage
- Storage component: `var storage: DailyNotificationStorage?` - **Line 35**
- Scheduler component: `var scheduler: DailyNotificationScheduler?` - **Line 36**
#### iOS-2 Branch
- **Note**: Check `ios-2` branch for alternative implementations:
```bash
git checkout ios-2
# Compare ios/Plugin/ implementations
```
---
## Testing Tools & Commands
### Android Testing
```bash
# Check scheduled alarms
adb shell dumpsys alarm | grep -i timesafari
# Force kill (not force-stop) - adjust package name based on test app
adb shell am kill com.timesafari.dailynotification
# Or for test apps:
# adb shell am kill com.timesafari.androidtestapp
# adb shell am kill <package-name-from-test-app-manifest>
# View logs
adb logcat | grep -i "DN\|DailyNotification"
# Check WorkManager tasks
adb shell dumpsys jobscheduler | grep -i timesafari
# Build and install test app
cd test-apps/android-test-app
./gradlew installDebug
```
### iOS Testing
```bash
# View device logs (requires Xcode)
xcrun simctl spawn booted log stream --predicate 'processImagePath contains "DailyNotification"'
# List pending notifications (requires app code)
# Use UNUserNotificationCenter.getPendingNotificationRequests()
# Build test app (from test-apps/ios-test-app)
# Use Xcode or:
cd test-apps/ios-test-app
# Follow build instructions in test app README
# Check ios-2 branch for alternative implementations
git checkout ios-2
# Compare ios/Plugin/ implementations between branches
```
---
## Next Steps After Exploration
Once this exploration is complete:
1. **Document findings** in the deliverables listed above
2. **Identify gaps** between current behavior and desired behavior
3. **Create implementation directives** to address gaps (if needed)
4. **Update plugin documentation** to accurately reflect platform limitations
5. **Update API documentation** with appropriate warnings and caveats