#!/bin/bash # Phase 1 Testing Script - Interactive Test Runner # Guides through all Phase 1 tests with clear prompts for UI interaction set -e # Exit on error # Source shared library SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/alarm-test-lib.sh" # Phase 1 specific configuration VERIFY_FIRE=${VERIFY_FIRE:-false} # Set VERIFY_FIRE=true to enable alarm fire verification APP_DIR="${SCRIPT_DIR}" PROJECT_ROOT="$(cd "${APP_DIR}/../.." && pwd)" # Allow selecting specific tests on the command line (e.g. ./test-phase1.sh 1 2) SELECTED_TESTS=() check_plugin_configured() { print_info "Checking if plugin is already configured..." # Wait a moment for app to fully load sleep 3 # Check if database exists (indicates plugin has been used) DB_EXISTS=$($ADB_BIN shell run-as "${APP_ID}" ls databases/ 2>/dev/null | grep -c "daily_notification" || echo "0") # Check if SharedPreferences has configuration (more reliable) # The plugin stores config in SharedPreferences PREFS_EXISTS=$($ADB_BIN shell run-as "${APP_ID}" ls shared_prefs/ 2>/dev/null | grep -c "DailyNotification" || echo "0") # Check recent logs for configuration activity RECENT_CONFIG=$($ADB_BIN logcat -d -t 50 | grep -E "Plugin configured|configurePlugin|Configuration" | tail -3) if [ "${DB_EXISTS}" -gt "0" ] || [ "${PREFS_EXISTS}" -gt "0" ]; then print_success "Plugin appears to be configured (database or preferences exist)" # Show user what to check in UI print_info "Please verify in the app UI that you see:" echo " âš™ī¸ Plugin Settings: ✅ Configured" echo " 🔌 Native Fetcher: ✅ Configured" echo "" echo "If both show ✅, the plugin is configured and you can skip configuration." echo "If either shows ❌ or 'Not configured', you'll need to click 'Configure Plugin'." echo "" return 0 else print_info "Plugin not configured (no database or preferences found)" print_info "You will need to click 'Configure Plugin' in the app UI" return 1 fi } check_permissions() { print_info "Checking notification permissions..." # Check if POST_NOTIFICATIONS permission is granted (Android 13+) PERM_CHECK=$($ADB_BIN shell dumpsys package "${APP_ID}" | grep -A 5 "granted=true" | grep -c "android.permission.POST_NOTIFICATIONS" || echo "0") # Also check via app's permission status if available PERM_GRANTED=false if [ "${PERM_CHECK}" -gt "0" ]; then PERM_GRANTED=true else # Check if we're on Android 12 or below (permission not required) SDK_VERSION=$($ADB_BIN shell getprop ro.build.version.sdk) if [ "${SDK_VERSION}" -lt "33" ]; then PERM_GRANTED=true # Pre-Android 13 doesn't need runtime permission fi fi if [ "${PERM_GRANTED}" = true ]; then print_success "Notification permissions granted" return 0 else print_warn "Notification permissions may not be granted" return 1 fi } ensure_permissions() { if check_permissions; then print_success "Permissions already granted" return 0 else print_info "Notification permissions needed" wait_for_ui_action "In the app UI, click the 'Request Permissions' button. This will show a system permission dialog. Steps: 1. Click 'Request Permissions' button 2. In the system dialog, tap 'Allow' to grant notification permission 3. Return to the app and verify the status shows: - 🔔 Notifications: ✅ Granted (or similar) Once permission is granted, press Enter to continue." # Re-check permissions sleep 2 if check_permissions; then print_success "Permissions granted" return 0 else print_warn "Permissions may still not be granted - continuing anyway" print_info "If notifications fail, you may need to grant permissions manually" return 1 fi fi } ensure_plugin_configured() { if check_plugin_configured; then # Plugin might be configured, but let user verify wait_for_ui_action "Please check the Plugin Status section at the top of the app. If you see: - âš™ī¸ Plugin Settings: ✅ Configured - 🔌 Native Fetcher: ✅ Configured - 🔔 Notifications: ✅ Granted (or similar) Then the plugin is already configured - just press Enter to continue. If any show ❌ or 'Not configured': - Click 'Request Permissions' if notifications are not granted - Click 'Configure Plugin' if settings/fetcher are not configured - Wait for all to show ✅, then press Enter." # Give a moment for any configuration that just happened sleep 2 print_success "Continuing with tests (plugin configuration verified or skipped)" return 0 else # Plugin definitely needs configuration print_info "Plugin needs configuration" # First ensure permissions ensure_permissions wait_for_ui_action "Click the 'Configure Plugin' button in the app UI. Wait for the status to update: - âš™ī¸ Plugin Settings: Should change to ✅ Configured - 🔌 Native Fetcher: Should change to ✅ Configured Once both show ✅, press Enter to continue." # Verify configuration completed sleep 2 print_success "Plugin configuration completed (or verified)" fi } # kill_app is now provided by the library, but Phase 1 uses it with PACKAGE variable # The library version uses APP_ID, which is set to PACKAGE, so it should work # But we keep this as a compatibility wrapper if needed # Phase 1 specific helper: get_current_time (used for fire verification) get_current_time() { $ADB_BIN shell date +%s } # Main test execution main() { # Allow selecting specific tests: e.g. `./test-phase1.sh 1 2` if [[ "$#" -gt 0 && ( "$1" == "-h" || "$1" == "--help" ) ]]; then echo "Usage: $0 [TEST_IDS...]" echo "" echo "If no TEST_IDS are given, all tests (1, 2, 3, 4) will run." echo "Examples:" echo " $0 # run all tests" echo " $0 1 # run only TEST 1" echo " $0 2 3 # run only TEST 2 and TEST 3" echo " $0 4 # run only TEST 4" return 0 fi SELECTED_TESTS=("$@") print_header "Phase 1 Testing Script" echo "This script will guide you through Phase 1 tests." echo "You'll be prompted when UI interaction is needed." echo "" wait_for_user # Pre-flight checks print_header "Pre-Flight Checks" check_adb_connection check_emulator_ready # Build and install build_app install_app # Clear logs clear_logs # ============================================ # TEST 0: Daily Rollover (Core Contract Verification) # ============================================ # Note: This test verifies the core "one notification per day" contract # by checking that after a notification fires, the next day's schedule # is correctly computed and scheduled. This is a manual/semi-automated test # as it requires either waiting for the alarm to fire or manipulating time. # # To run: Use test ID "0" or enable manually # ============================================ if should_run_test "0" SELECTED_TESTS; then print_header "TEST 0: Daily Rollover Verification" echo "Purpose: Verify that after a notification fires, the next day's" echo " schedule is correctly computed and only ONE alarm exists." echo "" echo "Note: This test verifies the core 'one notification per day' contract." echo " It requires either:" echo " 1. Scheduling a notification for 'now + N seconds' and waiting, OR" echo " 2. Manipulating the emulator clock to cross the fire boundary." echo "" echo " For now, this is a MANUAL test - you'll need to verify the" echo " behavior by checking logs and AlarmManager after a notification fires." echo "" wait_for_user print_step "1" "Schedule a test notification for near-future (or use existing)..." launch_app ensure_plugin_configured # Capture pre-schedule UI state take_screenshot "phase1_test0_daily_rollover" "before_scheduling" INITIAL_COUNT=$(get_plugin_alarm_count) SYSTEM_COUNT=$(get_system_alarm_count) print_info "Current notification alarms: ${INITIAL_COUNT} (expected before scheduling: 0)" print_info "System/other alarms: ${SYSTEM_COUNT} (for context)" print_info "Note: Prefetch uses WorkManager (not AlarmManager), so it won't appear in alarm count" if [ "${INITIAL_COUNT}" -eq "0" ] 2>/dev/null; then print_success "✅ No existing notification alarms (clean state)" wait_for_ui_action "In the app UI, schedule a daily notification. For this test, you may want to schedule it for a time very soon (e.g., 1-2 minutes from now) to observe the rollover behavior. This will schedule: - 1 notification alarm (AlarmManager) for the specified time - 1 prefetch job (WorkManager) for 2 minutes before that time" sleep 3 # Give alarm time to be registered in AlarmManager # Capture post-schedule UI state take_screenshot "phase1_test0_daily_rollover" "after_scheduling" POST_SCHEDULE_COUNT=$(get_plugin_alarm_count) # Check alarm count after scheduling # Note: This is a PRELIMINARY sanity check only - final verdict comes after rollover # 0 alarms is likely a race condition (alarm may not be visible yet in dumpsys) # Only treat >1 alarms as a real failure (duplicates) if [ "${POST_SCHEDULE_COUNT}" -eq "0" ] 2>/dev/null; then print_warn "âš ī¸ Found 0 plugin alarms right after scheduling." print_info "â„šī¸ Pre-schedule check not reliable; will rely on rollover assertions." print_info "â„šī¸ The alarm may not be visible in dumpsys yet. Continuing to verify after rollover." elif [ "${POST_SCHEDULE_COUNT}" -eq "1" ] 2>/dev/null; then print_success "✅ Found 1 notification alarm (expected: 1) – preliminary check passed." print_info "â„šī¸ This is preliminary check only; final verdict after rollover." else # count > 1 - this is a real duplicate bug print_warn "âš ī¸ âš ī¸ Found ${POST_SCHEDULE_COUNT} notification alarms (expected: 1) – DUPLICATES DETECTED right after scheduling!" print_warn "This indicates duplicate NOTIFICATION alarms were created (BUG DETECTED)" print_info "Showing alarm blocks for debugging:" show_plugin_alarms_compact print_info "For more details, run:" print_info " adb shell dumpsys alarm | grep -A 5 'com.timesafari.dailynotification' | sed -n '1,80p'" fi INITIAL_COUNT="${POST_SCHEDULE_COUNT}" fi # Only show alarm details if we found exactly 1 alarm (skip if 0 due to race condition) if [ "${INITIAL_COUNT}" -eq "1" ] 2>/dev/null; then print_success "✅ Single notification alarm scheduled (one per day)" print_info "Note: Prefetch uses WorkManager (not AlarmManager), so it won't appear in alarm count" # Show alarm details ALARM_DETAILS=$($ADB_BIN shell dumpsys alarm | grep -A 3 "com.timesafari.dailynotification" | grep -A 3 "com.timesafari.daily.NOTIFICATION" | head -10) if [ -n "${ALARM_DETAILS}" ]; then print_info "Notification alarm details:" echo "${ALARM_DETAILS}" | head -5 echo "" # Extract trigger time ALARM_TRIGGER_MS=$(echo "${ALARM_DETAILS}" | grep -oE "origWhen [0-9]+" | head -1 | awk '{print $2}') if [ -n "${ALARM_TRIGGER_MS}" ]; then ALARM_TRIGGER_SEC=$((ALARM_TRIGGER_MS / 1000)) ALARM_READABLE=$(date -d "@${ALARM_TRIGGER_SEC}" 2>/dev/null || echo "${ALARM_TRIGGER_MS} ms") print_info "Notification alarm scheduled for: ${ALARM_READABLE}" # Calculate prefetch time (2 minutes before) PREFETCH_SEC=$((ALARM_TRIGGER_SEC - 120)) PREFETCH_READABLE=$(date -d "@${PREFETCH_SEC}" 2>/dev/null || echo "${PREFETCH_SEC}") print_info "Prefetch should be scheduled for: ${PREFETCH_READABLE} (2 minutes before, via WorkManager)" # Calculate next day NEXT_DAY_SEC=$((ALARM_TRIGGER_SEC + 86400)) NEXT_DAY_READABLE=$(date -d "@${NEXT_DAY_SEC}" 2>/dev/null || echo "${NEXT_DAY_SEC}") print_info "Expected next day notification: ${NEXT_DAY_READABLE} (24 hours later)" fi fi elif [ "${INITIAL_COUNT}" -gt "1" ] 2>/dev/null; then print_warn "âš ī¸ Found ${INITIAL_COUNT} notification alarms (expected: 1) - DUPLICATES DETECTED!" print_warn "This indicates the duplicate alarm bug - multiple alarms for the same notification" fi # Note: We intentionally don't print anything for INITIAL_COUNT == 0 here # because we already handled it above with the race condition message print_step "2" "Manual verification steps..." echo "" echo "To complete this test, you need to:" echo " 1. Wait for the notification to fire (or advance emulator clock)" echo " 2. Check that the plugin:" echo " - Computed the next day's time (24 hours later)" echo " - Scheduled exactly ONE notification alarm (AlarmManager) for tomorrow" echo " - Scheduled exactly ONE prefetch job (WorkManager) for 2 minutes before tomorrow's notification" echo " - Did NOT create duplicate notification alarms" echo " 3. Verify in logs:" echo " - Next run time calculation shows tomorrow's time" echo " - Only one notification alarm scheduled in AlarmManager" echo " - Prefetch job scheduled in WorkManager" echo "" echo "Expected log patterns:" echo " DNP-SCHEDULE: Scheduling next daily alarm: ... source=ROLLOVER_ON_FIRE" echo " DNP-NOTIFY: Scheduling alarm: triggerTime=" echo " DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, ..." echo "" echo "When scheduling the test notification, expect:" echo " DNP-SCHEDULE: ... source=TEST_NOTIFICATION" echo "" echo "Alarm shape in dumpsys:" echo " RTC_WAKEUP, ALARM_CLOCK, tag=*walarm*:com.timesafari.daily.NOTIFICATION" echo "" echo "After notification fires, run:" echo " adb shell dumpsys alarm | grep -A 3 'com.timesafari.dailynotification'" echo " adb logcat -d | grep -E 'DNP-SCHEDULE|DNP-NOTIFY' | tail -20" echo "" wait_for_ui_action "After the notification fires (or you advance the clock), check the logs and AlarmManager to verify: 1. Only ONE alarm exists (one per day) 2. The alarm time is for tomorrow (24 hours later) 3. No duplicate alarms were created Press Enter when you've verified this (or to skip this test)." print_info "Polling for stable alarm count (allowing up to ~10 seconds for Android to settle)..." POST_ROLLOVER_COUNT=$(wait_for_stable_plugin_alarm_count 5 2) SYSTEM_FINAL=$(get_system_alarm_count) print_info "Notification alarms after rollover: ${POST_ROLLOVER_COUNT} (expected: 1)" print_info "System/other alarms: ${SYSTEM_FINAL} (for context)" print_info "Note: Prefetch is scheduled via WorkManager (not AlarmManager), so it won't appear in alarm count" # Capture post-rollover UI state take_screenshot "phase1_test0_daily_rollover" "after_rollover_check" # After rollover, the state should be stable - this is the real assertion point if [ "${POST_ROLLOVER_COUNT}" -eq "1" ] 2>/dev/null; then print_success "✅ TEST 0 PASSED: Daily rollover created exactly one NOTIFICATION alarm for tomorrow." print_info "Expected state after rollover:" echo " ✅ 1 notification alarm (AlarmManager) for tomorrow" echo " ✅ 1 prefetch job (WorkManager) for 2 minutes before tomorrow's notification" elif [ "${POST_ROLLOVER_COUNT}" -gt "1" ] 2>/dev/null; then print_warn "âš ī¸ âš ī¸ TEST 0 FAILED: Daily rollover created ${POST_ROLLOVER_COUNT} NOTIFICATION alarms (duplicates)." print_warn "This indicates duplicate NOTIFICATION alarms were created (BUG DETECTED)" take_failure_screenshot "phase1_test0_daily_rollover" "duplicate_alarms" echo "" print_info "Showing alarm blocks for debugging:" show_plugin_alarms_compact echo "" print_info "Check scheduling logs for multiple DNP-SCHEDULE entries:" print_info " adb logcat -d | grep 'DNP-SCHEDULE\|DNP-NOTIFY' | tail -20" echo "" print_info "Possible causes:" echo " - Multiple scheduling paths racing (rollover + recovery)" echo " - Old alarm wasn't canceled before scheduling new one" echo " - Different scheduleIds used for same trigger time" echo " - Idempotence check not working correctly" else # count is 0 or invalid - rollover may have failed print_warn "âš ī¸ TEST 0 INCONCLUSIVE: No NOTIFICATION alarm found after rollover (expected 1, got 0)" print_warn "This indicates the rollover did not schedule tomorrow's alarm correctly" take_failure_screenshot "phase1_test0_daily_rollover" "no_alarm_after_rollover" echo "" print_info "Showing alarm blocks for debugging:" show_plugin_alarms_compact echo "" print_info "Expected log pattern after fire:" echo " DNP-SCHEDULE: Scheduling next daily alarm: ... source=ROLLOVER_ON_FIRE" echo " DNP-NOTIFY: Scheduling alarm: triggerTime=" echo " DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, ..." echo "" print_info "Check logs for rollover scheduling errors:" print_info " adb logcat -d | grep 'DNP-SCHEDULE\|DNP-NOTIFY' | tail -20" echo "" print_info "Manual verification:" echo " If dumpsys shows a single RTC_WAKEUP alarm with" echo " tag=*walarm*:com.timesafari.daily.NOTIFICATION for tomorrow," echo " treat TEST 0 as manually PASSED and update the script expectations later." fi wait_for_user fi # ============================================ # TEST 1: Force-Stop Recovery - Database Restoration # ============================================ if should_run_test "1" SELECTED_TESTS; then print_header "TEST 1: Force-Stop Recovery - Database Restoration" echo "Purpose: Verify that after force-stop (which clears alarms), recovery" echo " uses the database to rebuild alarms on app relaunch." echo "" echo "Note: App supports one alarm per day." echo "" echo "Test sequence:" echo " 1. Clean start - verify no lingering alarms" echo " 2. Schedule one alarm → Verify it exists in AlarmManager" echo " 3. Force-stop app → Verify alarm is cleared (count = 0)" echo " 4. Relaunch app → Verify recovery rebuilds alarm from database" echo " 5. Verify alarm actually fires at scheduled time (optional)" echo "" wait_for_user # ============================================ # Step 1: Clean start - verify no lingering alarms # ============================================ print_step "1" "Clean start - checking for lingering alarms..." LINGERING_COUNT=$(get_plugin_alarm_count) SYSTEM_COUNT=$(get_system_alarm_count) print_info "Current plugin notification alarms: ${LINGERING_COUNT}" print_info "System/other alarms: ${SYSTEM_COUNT} (for context)" if [ "${LINGERING_COUNT}" -gt "0" ] 2>/dev/null; then print_warn "Found ${LINGERING_COUNT} lingering plugin alarm(s) - these will interfere with TEST 1." print_info "TEST 1 needs a clean state (no existing plugin alarms)." print_info "Resetting app state via uninstall + reinstall..." # Uninstall existing app print_info "Uninstalling existing app..." set +e uninstall_output="$($ADB_BIN uninstall "$APP_ID" 2>&1)" uninstall_status=$? set -e if [ $uninstall_status -eq 0 ]; then print_success "App uninstall succeeded (clean slate)." else if grep -q "DELETE_FAILED_INTERNAL_ERROR" <<<"$uninstall_output"; then print_info "No existing app to uninstall (continuing)" else print_warn "App uninstall reported an error: $uninstall_output (continuing anyway)" fi fi # Reinstall APK print_info "Reinstalling APK..." if $ADB_BIN install -r "$APK_PATH" >/dev/null 2>&1; then print_success "App reinstall succeeded." else print_error "App reinstall FAILED - cannot proceed with TEST 1." print_warn "Marking TEST 1 as INCONCLUSIVE due to dirty starting state." take_failure_screenshot "phase1_test1_force_stop" "dirty_state_reinstall_failed" wait_for_user # Skip to end of TEST 1 block goto_test1_end=true fi if [ "${goto_test1_end:-false}" != "true" ]; then # Verify install if ! $ADB_BIN shell pm list packages | grep -q "$APP_ID"; then print_error "App not found in package list after reinstall - aborting TEST 1." take_failure_screenshot "phase1_test1_force_stop" "dirty_state_verify_failed" wait_for_user goto_test1_end=true fi fi if [ "${goto_test1_end:-false}" != "true" ]; then # Clear logs print_info "Clearing logcat buffer after reinstall..." $ADB_BIN logcat -c || true # Re-check plugin alarms print_info "Rechecking plugin alarms after reset..." sleep 2 # Give system time to clear alarms LINGERING_COUNT=$(get_plugin_alarm_count) print_info "Plugin alarms after reset: ${LINGERING_COUNT} (expected: 0)" if [ "${LINGERING_COUNT}" -ne "0" ] 2>/dev/null; then print_warn "TEST 1 starting with non-zero alarm count even after reset; treating as INCONCLUSIVE." print_info "This may indicate device-specific behavior where alarms persist across uninstall." take_failure_screenshot "phase1_test1_force_stop" "unexpected_alarms_after_reset" wait_for_user goto_test1_end=true fi fi if [ "${goto_test1_end:-false}" = "true" ]; then print_warn "TEST 1 skipped due to inability to achieve clean starting state." wait_for_user else print_success "App state reset complete. TEST 1 starting from clean state." fi else print_success "No lingering plugin alarms found (clean state)" fi # Skip remaining TEST 1 steps if we couldn't achieve clean state if [ "${goto_test1_end:-false}" != "true" ]; then # ============================================ # Step 2: Schedule a known future alarm # ============================================ print_step "2" "Launch app and schedule alarm..." launch_app ensure_plugin_configured wait_for_ui_action "In the app UI, click the 'Test Notification' button. This will schedule ONE notification for 4 minutes in the future. (App supports one alarm per day) The alarm will be stored in the database AND scheduled in AlarmManager." print_step "3" "Verifying alarm exists in AlarmManager (BEFORE force-stop)..." sleep 2 ALARM_COUNT_BEFORE=$(get_plugin_alarm_count) SYSTEM_COUNT_BEFORE=$(get_system_alarm_count) print_info "Plugin alarms: ${ALARM_COUNT_BEFORE} (expected: 1)" print_info "System/other alarms: ${SYSTEM_COUNT_BEFORE} (for context)" if [ "${ALARM_COUNT_BEFORE}" -eq "0" ] 2>/dev/null; then print_error "No plugin alarms found in AlarmManager - cannot test force-stop recovery" print_info "Make sure you clicked 'Test Notification' and wait a moment" wait_for_user # Re-check ALARM_COUNT_BEFORE=$(get_plugin_alarm_count) if [ "${ALARM_COUNT_BEFORE}" -eq "0" ] 2>/dev/null; then print_error "Still no alarms found - aborting test" exit 1 fi fi if [ "${ALARM_COUNT_BEFORE}" -eq "1" ] 2>/dev/null; then print_success "✅ Single plugin alarm confirmed in AlarmManager (one per day)" else print_warn "âš ī¸ Found ${ALARM_COUNT_BEFORE} plugin alarms (expected: 1) - continuing anyway" fi # Capture alarm details for later verification ALARM_DETAILS_BEFORE=$($ADB_BIN shell dumpsys alarm | grep -A 3 "$APP_ID" | head -10) print_info "Alarm details:" echo "${ALARM_DETAILS_BEFORE}" | head -5 echo "" # Extract trigger time for later verification ALARM_TRIGGER_MS=$(echo "${ALARM_DETAILS_BEFORE}" | grep -oE "origWhen [0-9]+" | head -1 | awk '{print $2}') if [ -n "${ALARM_TRIGGER_MS}" ]; then ALARM_TRIGGER_SEC=$((ALARM_TRIGGER_MS / 1000)) ALARM_READABLE=$(date -d "@${ALARM_TRIGGER_SEC}" 2>/dev/null || echo "${ALARM_TRIGGER_SEC}") print_info "Alarm scheduled for: ${ALARM_READABLE} (${ALARM_TRIGGER_MS} ms)" fi print_info "Checking logs for scheduling confirmation..." $ADB_BIN logcat -d | grep -E "DN|SCHEDULE|Stored notification content" | tail -5 echo "" wait_for_user # ============================================ # Step 3: Force-stop the app (clears alarms) # ============================================ print_step "4" "Force-stopping app (clears all alarms)..." print_warn "Force-stop will clear ALL alarms from AlarmManager" print_info "Executing: $ADB_BIN shell am force-stop ${APP_ID}" force_stop_app sleep 2 # Verify app is stopped (force_stop_app already handles this) # ============================================ # Step 4: Verify alarms are MISSING (cleared by OS) # ============================================ print_step "5" "Verifying alarms are MISSING from AlarmManager (AFTER force-stop)..." sleep 1 ALARM_COUNT_AFTER=$(get_plugin_alarm_count) SYSTEM_COUNT_AFTER=$(get_system_alarm_count) print_info "Plugin alarms after force-stop: ${ALARM_COUNT_AFTER} (expected: 0)" print_info "System/other alarms: ${SYSTEM_COUNT_AFTER} (for context)" if [ "${ALARM_COUNT_AFTER}" -eq "0" ] 2>/dev/null; then print_success "✅ Plugin alarms cleared by force-stop (count: ${ALARM_COUNT_AFTER})" print_info "This confirms: Force-stop cleared alarms from AlarmManager" else print_warn "âš ī¸ Plugin alarms still present after force-stop (count: ${ALARM_COUNT_AFTER})" print_info "Some devices/OS versions may not clear alarms on force-stop" print_info "Continuing test anyway - recovery should still work" fi wait_for_user # ============================================ # Step 5: Relaunch app (triggers recovery from database) # ============================================ print_step "6" "Relaunching app (triggers recovery from database)..." clear_logs launch_app sleep 4 # Give recovery time to run # ============================================ # Step 6: Verify recovery rebuilt alarms from database # ============================================ print_step "7" "Verifying recovery rebuilt alarms from database..." sleep 2 ALARM_COUNT_RECOVERED=$(get_plugin_alarm_count) SYSTEM_COUNT_RECOVERED=$(get_system_alarm_count) print_info "Plugin alarms after recovery: ${ALARM_COUNT_RECOVERED} (expected: 1)" print_info "System/other alarms: ${SYSTEM_COUNT_RECOVERED} (for context)" print_info "Checking recovery logs..." check_recovery_logs print_info "Expected log output:" echo " DNP-REACTIVATION: Starting app launch recovery" echo " DNP-REACTIVATION: Rescheduled alarm: for