From 07ace329821b8f56dbedab6a2f39eb503fd05cab Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 28 Nov 2025 08:53:42 +0000 Subject: [PATCH] refactor(test): extract shared helpers into alarm-test-lib.sh Extract common helper functions from test-phase1.sh, test-phase2.sh, and test-phase3.sh into a shared library (alarm-test-lib.sh) to reduce code duplication and improve maintainability. Changes: - Create alarm-test-lib.sh with shared configuration, UI helpers, ADB helpers, log parsing, and test selection logic - Refactor all three phase test scripts to source the shared library - Remove ~200 lines of duplicated code across the three scripts - Preserve all existing behavior, CLI arguments, and test semantics - Maintain Phase 1 compatibility (print_* functions, VERIFY_FIRE flag) - Update all adb references to use $ADB_BIN variable - Standardize alarm counting to use shared count_alarms() function Benefits: - Single source of truth for shared helpers - Easier maintenance (fix once, benefits all scripts) - Consistent behavior across all test phases - No functional changes to test execution or results --- test-apps/android-test-app/alarm-test-lib.sh | 364 +++++++++++ test-apps/android-test-app/test-phase1.sh | 625 +++++++++++-------- test-apps/android-test-app/test-phase2.sh | 312 ++------- test-apps/android-test-app/test-phase3.sh | 240 ++----- 4 files changed, 813 insertions(+), 728 deletions(-) create mode 100644 test-apps/android-test-app/alarm-test-lib.sh diff --git a/test-apps/android-test-app/alarm-test-lib.sh b/test-apps/android-test-app/alarm-test-lib.sh new file mode 100644 index 0000000..0e4c0d8 --- /dev/null +++ b/test-apps/android-test-app/alarm-test-lib.sh @@ -0,0 +1,364 @@ +#!/usr/bin/env bash + +# ======================================== +# Shared Alarm Test Library +# ======================================== +# +# Common helpers for Phase 1, 2, and 3 test scripts +# +# Usage: source this file in your test script: +# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# source "${SCRIPT_DIR}/alarm-test-lib.sh" +# +# Configuration can be overridden before sourcing: +# APP_ID="custom.package" source "${SCRIPT_DIR}/alarm-test-lib.sh" + +# --- Config Defaults (can be overridden before sourcing) --- + +: "${APP_ID:=com.timesafari.dailynotification}" +: "${APK_PATH:=./app/build/outputs/apk/debug/app-debug.apk}" +: "${ADB_BIN:=adb}" + +# Reactivation log tag (common across phases) +: "${REACTIVATION_TAG:=DNP-REACTIVATION}" +: "${SCENARIO_KEY:=Detected scenario: }" + +# Derived config (for backward compatibility with Phase 1) +PACKAGE="${APP_ID}" +ACTIVITY="${APP_ID}/.MainActivity" + +# Colors for output (used by Phase 1 print_* functions) +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# --- UI/Log Helpers --- + +section() { + echo + echo "========================================" + echo "$1" + echo "========================================" + echo +} + +substep() { + echo "→ $1" +} + +info() { + echo -e "ℹ️ $1" +} + +ok() { + echo -e "✅ $1" +} + +warn() { + echo -e "⚠️ $1" +} + +error() { + echo -e "❌ $1" +} + +pause() { + echo + read -rp "Press Enter when ready to continue..." + echo +} + +ui_prompt() { + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "👆 UI ACTION REQUIRED" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -e "$1" + echo + read -rp "Press Enter after completing the action above..." + echo +} + +# Phase 1 compatibility aliases (print_* functions) +print_header() { + echo "" + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}========================================${NC}" + echo "" +} + +print_step() { + echo -e "${GREEN}→ Step $1:${NC} $2" +} + +print_wait() { + echo -e "${YELLOW}⏳ $1${NC}" +} + +print_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +print_error() { + echo -e "${RED}❌ $1${NC}" +} + +print_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +print_warn() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +wait_for_user() { + echo "" + read -p "Press Enter when ready to continue..." + echo "" +} + +wait_for_ui_action() { + ui_prompt "$1" +} + +# --- ADB/Build Helpers --- + +require_adb_device() { + section "Pre-Flight Checks" + + if ! $ADB_BIN devices | awk 'NR>1 && $2=="device"{found=1} END{exit !found}'; then + error "No emulator/device in 'device' state. Start your emulator first." + exit 1 + fi + ok "ADB device connected" + + info "Checking emulator status..." + if ! $ADB_BIN shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then + info "Waiting for emulator to boot..." + $ADB_BIN wait-for-device + while [ "$($ADB_BIN shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do + sleep 2 + done + fi + ok "Emulator is ready" +} + +# Phase 1 compatibility: check_adb_connection + check_emulator_ready +check_adb_connection() { + if ! $ADB_BIN devices | grep -q "device$"; then + print_error "No Android device/emulator connected" + echo "Please connect a device or start an emulator, then run:" + echo " adb devices" + exit 1 + fi + print_success "ADB device connected" +} + +check_emulator_ready() { + print_info "Checking emulator status..." + if ! $ADB_BIN shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then + print_wait "Waiting for emulator to boot..." + $ADB_BIN wait-for-device + while [ "$($ADB_BIN shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do + sleep 2 + done + fi + print_success "Emulator is ready" +} + +build_app() { + section "Building Test App" + + substep "Step 1: Building debug APK..." + if ./gradlew :app:assembleDebug; then + ok "Build successful" + else + error "Build failed" + exit 1 + fi + + if [[ -f "$APK_PATH" ]]; then + ok "APK ready: $APK_PATH" + else + error "APK not found at $APK_PATH" + exit 1 + fi +} + +install_app() { + section "Installing App" + + substep "Step 1: Uninstalling existing app (if present)..." + set +e + uninstall_output="$($ADB_BIN uninstall "$APP_ID" 2>&1)" + uninstall_status=$? + set -e + + if [[ $uninstall_status -ne 0 ]]; then + if grep -q "DELETE_FAILED_INTERNAL_ERROR" <<<"$uninstall_output"; then + info "No existing app to uninstall (continuing)" + else + warn "Uninstall returned non-zero status: $uninstall_output (continuing anyway)" + fi + else + ok "Previous app uninstall succeeded" + fi + + substep "Step 2: Installing new APK..." + if $ADB_BIN install -r "$APK_PATH"; then + ok "App installed successfully" + else + error "App installation failed" + exit 1 + fi + + substep "Step 3: Verifying installation..." + if $ADB_BIN shell pm list packages | grep -q "$APP_ID"; then + ok "App verified in package list" + else + error "App not found in package list" + exit 1 + fi + + info "Clearing logcat buffer..." + $ADB_BIN logcat -c + ok "Logcat cleared" +} + +launch_app() { + # Check if we're in Phase 1 context (has print_info function) + if type print_info >/dev/null 2>&1; then + print_info "Launching app..." + $ADB_BIN shell am start -n "${ACTIVITY}" + sleep 3 # Give app time to load and check status + print_success "App launched" + else + substep "Launching app main activity..." + $ADB_BIN shell am start -n "${ACTIVITY}" >/dev/null 2>&1 + sleep 3 # Give app time to load + ok "App launched" + fi +} + +clear_logs() { + info "Clearing logcat buffer..." + $ADB_BIN logcat -c + ok "Logs cleared" +} + +show_alarms() { + info "Checking AlarmManager status..." + echo + $ADB_BIN shell dumpsys alarm | grep -A3 "$APP_ID" || true + echo +} + +count_alarms() { + # Returns count of alarms for our app (cleaned of newlines) + local count + count="$($ADB_BIN shell dumpsys alarm | grep -c "$APP_ID" 2>/dev/null || echo "0")" + echo "$count" | tr -d '\n\r' | head -1 +} + +force_stop_app() { + info "Forcing stop of app process..." + $ADB_BIN shell am force-stop "$APP_ID" || true + sleep 2 + ok "Force stop issued" +} + +# Phase 1 compatibility alias +kill_app() { + print_info "Killing app process..." + $ADB_BIN shell am kill "$APP_ID" + sleep 2 + + # Verify process is killed + if $ADB_BIN shell ps | grep -q "$APP_ID"; then + print_wait "Process still running, using force-stop..." + $ADB_BIN shell am force-stop "$APP_ID" + sleep 1 + fi + + if ! $ADB_BIN shell ps | grep -q "$APP_ID"; then + print_success "App process terminated" + else + print_error "App process still running" + return 1 + fi +} + +reboot_emulator() { + info "Rebooting emulator..." + $ADB_BIN reboot + ok "Reboot initiated" + + info "Waiting for emulator to come back online..." + $ADB_BIN wait-for-device + + info "Waiting for system to fully boot..." + while [ "$($ADB_BIN shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do + sleep 2 + done + sleep 5 # Additional buffer for system services + ok "Emulator back online" +} + +# --- Log Parsing Helpers --- + +get_recovery_logs() { + # Collect only the relevant reactivation logs + $ADB_BIN logcat -d | grep "$REACTIVATION_TAG" || true +} + +extract_field_from_logs() { + # Usage: extract_field_from_logs "$logs" "rescheduled" + local logs="$1" + local key="$2" + echo "$logs" | grep -E "$key=" | sed -E "s/.*$key=([0-9]+).*/\1/" | tail -n1 || echo "0" +} + +extract_scenario_from_logs() { + local logs="$1" + echo "$logs" | grep -E "$SCENARIO_KEY" | sed -E "s/.*$SCENARIO_KEY([A-Z_]+).*/\1/" | tail -n1 || echo "" +} + +# Phase 1 compatibility: check_recovery_logs +check_recovery_logs() { + print_info "Checking recovery logs..." + echo "" + $ADB_BIN logcat -d | grep -E "$REACTIVATION_TAG" | tail -10 + echo "" +} + +# Phase 1 compatibility: check_alarm_status +check_alarm_status() { + print_info "Checking AlarmManager status..." + echo "" + $ADB_BIN shell dumpsys alarm | grep -i "$APP_ID" | head -5 + echo "" +} + +# --- Test Selection Helper --- + +should_run_test() { + # Usage: should_run_test "1" SELECTED_TESTS + # Returns 0 (success) if test should run, 1 (failure) if not + local id="$1" + local -n selected_tests_ref="$2" + + # If no tests are specified, run all tests + if [[ "${#selected_tests_ref[@]}" -eq 0 ]]; then + return 0 + fi + + for t in "${selected_tests_ref[@]}"; do + if [[ "$t" == "$id" ]]; then + return 0 + fi + done + return 1 +} + diff --git a/test-apps/android-test-app/test-phase1.sh b/test-apps/android-test-app/test-phase1.sh index e15b5ab..e1e5d34 100755 --- a/test-apps/android-test-app/test-phase1.sh +++ b/test-apps/android-test-app/test-phase1.sh @@ -5,155 +5,17 @@ set -e # Exit on error -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color +# Source shared library +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/alarm-test-lib.sh" -# Configuration -PACKAGE="com.timesafari.dailynotification" -ACTIVITY="${PACKAGE}/.MainActivity" -APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# 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)" -# Functions -print_header() { - echo "" - echo -e "${BLUE}========================================${NC}" - echo -e "${BLUE}$1${NC}" - echo -e "${BLUE}========================================${NC}" - echo "" -} - -print_step() { - echo -e "${GREEN}→ Step $1:${NC} $2" -} - -print_wait() { - echo -e "${YELLOW}⏳ $1${NC}" -} - -print_success() { - echo -e "${GREEN}✅ $1${NC}" -} - -print_error() { - echo -e "${RED}❌ $1${NC}" -} - -print_info() { - echo -e "${BLUE}ℹ️ $1${NC}" -} - -wait_for_user() { - echo "" - read -p "Press Enter when ready to continue..." - echo "" -} - -wait_for_ui_action() { - echo "" - echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${YELLOW}👆 UI ACTION REQUIRED${NC}" - echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${BLUE}$1${NC}" - echo "" - read -p "Press Enter after completing the action above..." - echo "" -} - -check_adb_connection() { - if ! adb devices | grep -q "device$"; then - print_error "No Android device/emulator connected" - echo "Please connect a device or start an emulator, then run:" - echo " adb devices" - exit 1 - fi - print_success "ADB device connected" -} - -check_emulator_ready() { - print_info "Checking emulator status..." - if ! adb shell getprop sys.boot_completed | grep -q "1"; then - print_wait "Waiting for emulator to boot..." - adb wait-for-device - while [ "$(adb shell getprop sys.boot_completed)" != "1" ]; do - sleep 2 - done - fi - print_success "Emulator is ready" -} - -build_app() { - print_header "Building Test App" - - cd "${APP_DIR}" - - print_step "1" "Building debug APK..." - if ./gradlew assembleDebug; then - print_success "Build successful" - else - print_error "Build failed" - exit 1 - fi - - APK_PATH="${APP_DIR}/app/build/outputs/apk/debug/app-debug.apk" - if [ ! -f "${APK_PATH}" ]; then - print_error "APK not found at ${APK_PATH}" - exit 1 - fi - - print_success "APK ready: ${APK_PATH}" -} - -install_app() { - print_header "Installing App" - - print_step "1" "Uninstalling existing app (if present)..." - UNINSTALL_OUTPUT=$(adb uninstall "${PACKAGE}" 2>&1) - UNINSTALL_EXIT=$? - - if [ ${UNINSTALL_EXIT} -eq 0 ]; then - print_success "Existing app uninstalled" - elif echo "${UNINSTALL_OUTPUT}" | grep -q "DELETE_FAILED_INTERNAL_ERROR"; then - print_info "No existing app to uninstall (continuing)" - elif echo "${UNINSTALL_OUTPUT}" | grep -q "Failure"; then - print_info "Uninstall failed (app may not exist) - continuing with install" - else - print_info "Uninstall result unclear - continuing with install" - fi - - print_step "2" "Installing new APK..." - if adb install -r "${APP_DIR}/app/build/outputs/apk/debug/app-debug.apk"; then - print_success "App installed successfully" - else - print_error "Installation failed" - exit 1 - fi - - print_step "3" "Verifying installation..." - if adb shell pm list packages | grep -q "${PACKAGE}"; then - print_success "App verified in package list" - else - print_error "App not found in package list" - exit 1 - fi -} - -clear_logs() { - print_info "Clearing logcat buffer..." - adb logcat -c - print_success "Logs cleared" -} - -launch_app() { - print_info "Launching app..." - adb shell am start -n "${ACTIVITY}" - sleep 3 # Give app time to load and check status - print_success "App launched" -} +# 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..." @@ -162,14 +24,14 @@ check_plugin_configured() { sleep 3 # Check if database exists (indicates plugin has been used) - DB_EXISTS=$(adb shell run-as "${PACKAGE}" ls databases/ 2>/dev/null | grep -c "daily_notification" || echo "0") + 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 shell run-as "${PACKAGE}" ls shared_prefs/ 2>/dev/null | grep -c "DailyNotification" || echo "0") + 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 logcat -d -t 50 | grep -E "Plugin configured|configurePlugin|Configuration" | tail -3) + 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)" @@ -191,6 +53,64 @@ check_plugin_configured() { 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 @@ -199,11 +119,14 @@ ensure_plugin_configured() { 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 either shows ❌ or 'Not configured', click 'Configure Plugin' button first, - wait for both to show ✅, then press Enter." + 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 @@ -212,6 +135,10 @@ ensure_plugin_configured() { 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: @@ -226,48 +153,34 @@ ensure_plugin_configured() { fi } -kill_app() { - print_info "Killing app process..." - adb shell am kill "${PACKAGE}" - sleep 2 - - # Verify process is killed - if adb shell ps | grep -q "${PACKAGE}"; then - print_wait "Process still running, using force-stop..." - adb shell am force-stop "${PACKAGE}" - sleep 1 - fi - - if ! adb shell ps | grep -q "${PACKAGE}"; then - print_success "App process terminated" - else - print_error "App process still running" - return 1 - fi -} - -check_recovery_logs() { - print_info "Checking recovery logs..." - echo "" - adb logcat -d | grep -E "DNP-REACTIVATION" | tail -10 - echo "" -} - -check_alarm_status() { - print_info "Checking AlarmManager status..." - echo "" - adb shell dumpsys alarm | grep -i timesafari | head -5 - echo "" -} +# 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 shell date +%s + $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 all Phase 1 tests." + echo "This script will guide you through Phase 1 tests." echo "You'll be prompted when UI interaction is needed." echo "" wait_for_user @@ -285,112 +198,259 @@ main() { clear_logs # ============================================ - # TEST 1: Cold Start Missed Detection + # TEST 1: Force-Stop Recovery - Database Restoration # ============================================ - print_header "TEST 1: Cold Start Missed Detection" - echo "Purpose: Verify missed notifications are detected and marked." + 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 - print_step "1" "Launch app and check plugin status" + # ============================================ + # Step 1: Clean start - verify no lingering alarms + # ============================================ + print_step "1" "Clean start - checking for lingering alarms..." + LINGERING_COUNT=$(count_alarms) + if [ "${LINGERING_COUNT}" -gt "0" ] 2>/dev/null; then + print_warn "Found ${LINGERING_COUNT} lingering alarm(s) - these may interfere with test" + print_info "Consider uninstalling/reinstalling app for clean state" + wait_for_user + else + print_success "No lingering alarms found (clean state)" + fi + + # ============================================ + # 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 a notification for 4 minutes in the future. - (The test app automatically schedules for 4 minutes from now)" + This will schedule ONE notification for 4 minutes in the future. + (App supports one alarm per day) - print_step "2" "Verifying notification was scheduled..." + 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 - check_alarm_status + + ALARM_COUNT_BEFORE=$(count_alarms) + print_info "Alarm count in AlarmManager: ${ALARM_COUNT_BEFORE}" + + if [ "${ALARM_COUNT_BEFORE}" -eq "0" ] 2>/dev/null; then + print_error "No 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=$(count_alarms) + if [ "${ALARM_COUNT_BEFORE}" -eq "0" ] 2>/dev/null; then + print_error "Still no alarms found - aborting test" + exit 1 + fi + fi + + print_success "✅ Alarms confirmed in AlarmManager (count: ${ALARM_COUNT_BEFORE})" + + # 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 logcat -d | grep -E "DN|SCHEDULE|Stored notification content" | tail -5 - - wait_for_ui_action "Verify in the logs above that you see: - - 'Stored notification content in database' (NEW - should appear now) - - Alarm scheduled in AlarmManager - - If you don't see 'Stored notification content', the fix may not be working." + $ADB_BIN logcat -d | grep -E "DN|SCHEDULE|Stored notification content" | tail -5 + echo "" wait_for_user - print_step "3" "Killing app process (simulates OS kill)..." - kill_app + # ============================================ + # 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 - print_step "4" "Getting alarm scheduled time..." - ALARM_INFO=$(adb shell dumpsys alarm | grep -i timesafari | grep "origWhen" | head -1) - if [ -n "${ALARM_INFO}" ]; then - # Extract alarm time (origWhen is in milliseconds) - ALARM_TIME_MS=$(echo "${ALARM_INFO}" | grep -oE 'origWhen [0-9]+' | awk '{print $2}') - if [ -n "${ALARM_TIME_MS}" ]; then - CURRENT_TIME=$(get_current_time) - ALARM_TIME_SEC=$((ALARM_TIME_MS / 1000)) - WAIT_SECONDS=$((ALARM_TIME_SEC - CURRENT_TIME + 60)) # Wait 1 minute past alarm - - if [ ${WAIT_SECONDS} -gt 0 ] && [ ${WAIT_SECONDS} -lt 600 ]; then - ALARM_READABLE=$(date -d "@${ALARM_TIME_SEC}" 2>/dev/null || echo "${ALARM_TIME_SEC}") - CURRENT_READABLE=$(date -d "@${CURRENT_TIME}" 2>/dev/null || echo "${CURRENT_TIME}") - print_info "Alarm scheduled for: ${ALARM_READABLE}" - print_info "Current time: ${CURRENT_READABLE}" - print_wait "Waiting ${WAIT_SECONDS} seconds for alarm time to pass..." - sleep ${WAIT_SECONDS} - elif [ ${WAIT_SECONDS} -le 0 ]; then - print_info "Alarm time has already passed" - print_wait "Waiting 2 minutes to ensure we're well past alarm time..." - sleep 120 - else - print_wait "Alarm is more than 10 minutes away. Waiting 5 minutes (you can adjust this)..." - sleep 300 - fi - else - print_wait "Could not parse alarm time. Waiting 5 minutes..." - sleep 300 - fi + # 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=$(count_alarms) + print_info "Alarm count in AlarmManager after force-stop: ${ALARM_COUNT_AFTER}" + + if [ "${ALARM_COUNT_AFTER}" -eq "0" ] 2>/dev/null; then + print_success "✅ Alarms cleared by force-stop (count: ${ALARM_COUNT_AFTER})" + print_info "This confirms: Force-stop cleared alarms from AlarmManager" else - print_wait "Could not find alarm in AlarmManager. Waiting 5 minutes..." - sleep 300 + print_warn "⚠️ 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 - print_step "5" "Launching app (cold start - triggers recovery)..." + 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 - print_step "6" "Checking recovery logs..." - sleep 3 + # ============================================ + # Step 6: Verify recovery rebuilt alarms from database + # ============================================ + print_step "7" "Verifying recovery rebuilt alarms from database..." + sleep 2 + ALARM_COUNT_RECOVERED=$(count_alarms) + print_info "Alarm count in AlarmManager after recovery: ${ALARM_COUNT_RECOVERED}" + + print_info "Checking recovery logs..." check_recovery_logs print_info "Expected log output:" - echo " DNP-REACTIVATION: Starting app launch recovery (Phase 1: cold start only)" - echo " DNP-REACTIVATION: Cold start recovery: checking for missed notifications" - echo " DNP-REACTIVATION: Marked missed notification: " - echo " DNP-REACTIVATION: Cold start recovery complete: missed=1, ..." + echo " DNP-REACTIVATION: Starting app launch recovery" + echo " DNP-REACTIVATION: Rescheduled alarm: for