Merge branch 'master' into ios-2

This commit is contained in:
Matthew
2025-12-08 22:42:23 -08:00
44 changed files with 17036 additions and 279 deletions

View File

@@ -0,0 +1,613 @@
#!/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: }"
# Screenshot configuration
: "${SCREENSHOT_ROOT:=screenshots}"
: "${ENABLE_SCREENSHOTS:=1}"
# 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
}
# Plugin-specific alarm action (must match AndroidManifest.xml)
PLUGIN_ALARM_ACTION="com.timesafari.daily.NOTIFICATION"
get_plugin_alarm_count() {
# Returns count of ONLY the plugin's NOTIFICATION alarms (not prefetch - that uses WorkManager)
# Expected: 1 notification alarm per daily schedule
#
# This function counts ALARM_CLOCK wake alarms (RTC_WAKEUP) tagged as:
# tag=*walarm*:com.timesafari.daily.NOTIFICATION
#
# Uses deduplicating parser to avoid double-counting the same alarm that appears in both:
# - Main alarm list
# - "Next wake from idle" section (ignored - only counts RTC_WAKEUP blocks)
# - Alarm Stats section (ignored - only counts actual alarm blocks)
#
# Tracks unique Alarm handles to ensure each alarm is counted only once.
# Checks for app package AND action string anywhere in the block (they appear on different lines).
local count app_id action
app_id="$APP_ID"
action="com.timesafari.daily.NOTIFICATION"
count="$($ADB_BIN shell dumpsys alarm 2>/dev/null | awk -v app="$app_id" -v action="$action" '
BEGIN {
in_block = 0
alarmId = ""
hasAppLine = 0
hasActionLine = 0
}
# Start of a new RTC_WAKEUP alarm block
/^[[:space:]]*RTC_WAKEUP/ {
# Flush previous block if it was a plugin notification
if (in_block == 1 && hasAppLine == 1 && hasActionLine == 1 && alarmId != "") {
seen[alarmId] = 1
}
in_block = 1
alarmId = ""
hasAppLine = 0
hasActionLine = 0
# Extract alarmId from "Alarm{11245c ..."
if (match($0, /Alarm\{[0-9a-f]+/)) {
# match is like "Alarm{11245c", extract just the hex part
alarmId = substr($0, RSTART + 6, RLENGTH - 6)
}
}
# Blank line = end of block
/^[[:space:]]*$/ {
if (in_block == 1 && hasAppLine == 1 && hasActionLine == 1 && alarmId != "") {
seen[alarmId] = 1
}
in_block = 0
alarmId = ""
hasAppLine = 0
hasActionLine = 0
}
# Lines inside an alarm block
in_block == 1 {
if ($0 ~ app) {
hasAppLine = 1
}
if ($0 ~ action) {
hasActionLine = 1
}
}
END {
# Flush final block if it was a plugin notification
if (in_block == 1 && hasAppLine == 1 && hasActionLine == 1 && alarmId != "") {
seen[alarmId] = 1
}
count = 0
for (id in seen) {
count++
}
print count + 0
}
' 2>/dev/null || echo "0")"
echo "$count" | tr -d '\n\r' | head -1
}
# Alias for backward compatibility
get_notification_alarm_count() {
get_plugin_alarm_count
}
get_prefetch_work_count() {
# Returns count of prefetch WorkManager jobs (not AlarmManager alarms)
# Note: Prefetch uses WorkManager, not AlarmManager, so it won't appear in dumpsys alarm
# This is a placeholder - actual WorkManager job counting would require different approach
local count
count="$($ADB_BIN shell dumpsys jobscheduler | grep -c "prefetch" 2>/dev/null || echo "0")"
echo "$count" | tr -d '\n\r' | head -1
}
get_system_alarm_count() {
# Returns total RTC_WAKEUP alarms on system (for debugging/context)
local count
count="$($ADB_BIN shell dumpsys alarm 2>/dev/null | grep -c "RTC_WAKEUP" || echo "0")"
echo "$count" | tr -d '\n\r' | head -1
}
count_alarms() {
# Legacy function: now returns plugin-specific alarm count
# Use get_plugin_alarm_count() for clarity, or get_system_alarm_count() for total
get_plugin_alarm_count
}
show_plugin_alarms_compact() {
# Prints only the plugin's alarm block(s) for debugging
# Shows complete RTC_WAKEUP alarm blocks that contain the app ID
# This makes it visually obvious why the AWK matcher should pick up alarms
# (app ID on header line, action on tag line within the same block)
$ADB_BIN shell dumpsys alarm 2>/dev/null \
| awk -v app="$APP_ID" '
BEGIN {
in_block = 0
block = ""
found_app = 0
}
/^[[:space:]]*RTC_WAKEUP/ {
# Print previous block if it contained app ID
if (in_block && found_app) {
print block ORS
}
# Start new block
in_block = 1
block = $0 ORS
found_app = ($0 ~ app) ? 1 : 0
next
}
/^[[:space:]]*$/ {
# End of block
if (in_block && found_app) {
print block ORS
}
in_block = 0
block = ""
found_app = 0
next
}
{
if (in_block) {
block = block $0 ORS
if ($0 ~ app) {
found_app = 1
}
}
}
END {
if (in_block && found_app) {
print block
}
}
' \
| sed -n '1,80p' || true
}
wait_for_stable_plugin_alarm_count() {
# Polls for plugin alarm count to stabilize (reduces race condition false negatives)
# Usage: wait_for_stable_plugin_alarm_count [attempts] [delay_seconds]
# Default: 5 attempts, 2 second delay (total ~10 seconds)
# Returns: alarm count (0 if none found after all attempts)
local attempts=${1:-5}
local delay=${2:-2}
local count=0
local i
for i in $(seq 1 "$attempts"); do
count="$(get_plugin_alarm_count)"
if [ "$count" -ge 1 ] 2>/dev/null; then
echo "$count"
return 0
fi
if [ "$i" -lt "$attempts" ]; then
sleep "$delay"
fi
done
echo "$count"
}
# --- Screenshot Helpers ---
take_screenshot() {
# Captures a device screenshot and saves it with test name, step, and timestamp
# Usage: take_screenshot "test_name" "step_name"
# Example: take_screenshot "phase1_test0_daily_rollover" "before_scheduling"
local test_name="$1"
local step_name="$2"
# Do nothing if screenshots are disabled
if [ "$ENABLE_SCREENSHOTS" != "1" ]; then
return 0
fi
if [ -z "$ADB_BIN" ]; then
echo "⚠️ ADB_BIN is not set; cannot take screenshot." >&2
return 0
fi
# Timestamp for uniqueness
local ts
ts="$(date '+%Y%m%d-%H%M%S' 2>/dev/null)" || ts="unknown"
# Directory: screenshots/<test_name>/
# Use absolute path relative to script directory if SCREENSHOT_ROOT is relative
local dir
if [ -n "$SCRIPT_DIR" ] && [ -d "$SCRIPT_DIR" ]; then
dir="${SCRIPT_DIR}/${SCREENSHOT_ROOT}/${test_name}"
elif [ -n "${BASH_SOURCE[0]}" ]; then
# Fallback: derive from this script's location
local lib_dir
lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd 2>/dev/null)" || lib_dir=""
if [ -n "$lib_dir" ]; then
dir="${lib_dir}/${SCREENSHOT_ROOT}/${test_name}"
else
dir="${SCREENSHOT_ROOT}/${test_name}"
fi
else
dir="${SCREENSHOT_ROOT}/${test_name}"
fi
mkdir -p "$dir" 2>/dev/null || {
echo "⚠️ Failed to create screenshot directory: $dir" >&2
return 0
}
# File name: <test>_<step>_<timestamp>.png, with spaces converted to dashes
local safe_step="${step_name// /-}"
local file="${dir}/${test_name}_${safe_step}_${ts}.png"
echo "📸 Capturing screenshot: ${file}" >&2
# Use exec-out to avoid newline mangling
if ! "$ADB_BIN" exec-out screencap -p > "$file" 2>/dev/null; then
echo "⚠️ Failed to capture screenshot via adb." >&2
# Clean up empty file if created
[ -s "$file" ] || rm -f "$file" 2>/dev/null || true
return 0
fi
# Verify file was created and has content
if [ ! -s "$file" ]; then
echo "⚠️ Screenshot file is empty or missing: $file" >&2
rm -f "$file" 2>/dev/null || true
return 0
fi
}
take_failure_screenshot() {
# Convenience wrapper for failure cases
# Usage: take_failure_screenshot "test_name" "reason"
# Example: take_failure_screenshot "phase1_test0_daily_rollover" "no_alarm_after_rollover"
local test_name="$1"
local reason="$2"
take_screenshot "$test_name" "FAIL_${reason}"
}
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
}

View File

@@ -17,6 +17,9 @@ android {
}
}
buildTypes {
debug {
debuggable true // Enable debugging for test app
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

View File

@@ -62,6 +62,11 @@
<div id="pluginStatusContent" style="margin-top: 8px;">
Loading plugin status...
</div>
<div id="notificationReceivedIndicator" style="margin-top: 8px; padding: 8px; background: rgba(0, 255, 0, 0.2); border-radius: 5px; display: none;">
<strong>🔔 Notification Received!</strong><br>
<span id="notificationReceivedTime"></span><br>
<small>Check the top of your screen for the notification banner</small>
</div>
</div>
</div>
@@ -232,7 +237,8 @@
const notificationTimeReadable = notificationTime.toLocaleTimeString();
status.innerHTML = '✅ Notification scheduled!<br>' +
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')';
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')<br><br>' +
'<small>💡 When the notification fires, look for a banner at the <strong>top of your screen</strong>.</small>';
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
// Refresh plugin status display
setTimeout(() => loadPluginStatus(), 500);
@@ -411,6 +417,39 @@
}
}
// Check for notification delivery periodically
function checkNotificationDelivery() {
if (!window.DailyNotification) return;
window.DailyNotification.getNotificationStatus()
.then(result => {
if (result.lastNotificationTime) {
const lastTime = new Date(result.lastNotificationTime);
const now = new Date();
const timeDiff = now - lastTime;
// If notification was received in the last 2 minutes, show indicator
if (timeDiff > 0 && timeDiff < 120000) {
const indicator = document.getElementById('notificationReceivedIndicator');
const timeSpan = document.getElementById('notificationReceivedTime');
if (indicator && timeSpan) {
indicator.style.display = 'block';
timeSpan.textContent = `Received at ${lastTime.toLocaleTimeString()}`;
// Hide after 30 seconds
setTimeout(() => {
indicator.style.display = 'none';
}, 30000);
}
}
}
})
.catch(error => {
// Silently fail - this is just for visual feedback
});
}
// Load plugin status automatically on page load
window.addEventListener('load', () => {
console.log('Page loaded, loading plugin status...');
@@ -419,6 +458,9 @@
loadPluginStatus();
loadPermissionStatus();
loadChannelStatus();
// Check for notification delivery every 5 seconds
setInterval(checkNotificationDelivery, 5000);
}, 500);
});

View File

@@ -0,0 +1,234 @@
# Phase 1 — TEST 0 Golden Run (Daily Rollover Verification)
**Last Updated:** 2025-12-04
**Status:** ✅ PASS (Golden Baseline)
**Related Docs:**
- [PHASE1_TEST0_GOLDEN.md](./PHASE1_TEST0_GOLDEN.md) - Daily Rollover Verification (this document)
- [PHASE1_TEST1_GOLDEN.md](./PHASE1_TEST1_GOLDEN.md) - Force-Stop Recovery
---
## 1. Test Overview
This document captures a **golden baseline** for **Phase 1 TEST 0: Daily Rollover Verification**.
**Purpose:** Verify that after a notification fires, the plugin:
- Computes **next day's time** (T + 24h)
- Schedules **exactly one** `AlarmManager` notification alarm for tomorrow
- Does **not** create duplicates
- Leaves prefetch in **WorkManager** (not visible in `dumpsys alarm`)
> **Golden Rule:** If a future run looks like this doc, TEST 0 should be considered a PASS.
---
## 2. Environment & Build Info
### Emulator / Device
- **API Level:** 35
- **Android Version:** 15
### Build
- **Gradle Version:** 8.13
- **Build Warnings:**
- `WARNING: Using flatDir should be avoided because it doesn't support any meta-data formats.`
- `Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.`
### Command Used
```bash
./test-phase1.sh
```
### Environment Variables
- `ENABLE_SCREENSHOTS=1` (screenshots enabled)
- `ADB_BIN=adb` (default)
---
## 3. Step-by-Step Execution (Golden Run)
1. Ran `./test-phase1.sh`.
2. Confirmed pre-flight checks (ADB device + emulator ready).
3. Allowed script to rebuild and reinstall the app.
4. Confirmed plugin status in the UI:
- ⚙️ Plugin Settings: ✅ Configured
- 🔌 Native Fetcher: ✅ Configured
- 🔔 Notifications: ✅ Granted
- ⏰ Exact Alarms: ✅ Granted
- 📢 Channel: ✅ Enabled (High)
5. From the UI, scheduled a **daily notification** for ~12 minutes in the future (scheduled for `09:23:00` on 2025-12-04).
6. Waited for the notification banner to fire.
7. Pressed Enter to continue when prompted.
8. Let the script perform the post-rollover alarm check.
---
## 4. Expected Script Output (Key Excerpts)
### 4.1. Pre-Schedule Check
```text
✅ Found 1 notification alarm (expected: 1) preliminary check passed.
This is preliminary check only; final verdict after rollover.
```
### 4.2. Notification Alarm Details (After Scheduling)
```text
Notification alarm details:
tag=*walarm*:com.timesafari.daily.NOTIFICATION
type=RTC_WAKEUP origWhen=2025-12-04 09:23:00.000 window=0 exactAllowReason=policy_permission repeatInterval=0 count=0 flags=0x3
policyWhenElapsed: requester=+3m34s315ms app_standby=-10s456ms device_idle=-- battery_saver=--
```
### 4.3. Post-Rollover Check
```text
Polling for stable alarm count (allowing up to ~10 seconds for Android to settle)...
Notification alarms after rollover: 1 (expected: 1)
System/other alarms: <N> (for context)
Note: Prefetch is scheduled via WorkManager (not AlarmManager), so it won't appear in alarm count
```
### 4.4. Final Verdict
```text
✅ TEST 0 PASSED: Daily rollover created exactly one NOTIFICATION alarm for tomorrow.
Expected state after rollover:
✅ 1 notification alarm (AlarmManager) for tomorrow
✅ 1 prefetch job (WorkManager) for 2 minutes before tomorrow's notification
```
**Note:** The `origWhen` for tomorrow will be the next day at the same time (e.g., `2025-12-05 09:23:00.000` if scheduled for `2025-12-04 09:23:00.000`).
---
## 5. Expected UI State (Screenshots)
### 5.1 Screenshot Files (Golden Run)
- `screenshots/phase1_test0_daily_rollover/phase1_test0_daily_rollover_before_scheduling_20251204-091910.png`
- **Status:** Active Schedules: **No**; Next Notification: **None scheduled**.
- `screenshots/phase1_test0_daily_rollover/phase1_test0_daily_rollover_after_scheduling_20251204-091925.png`
- **Status:** Active Schedules: **Yes**; Next Notification: **today at 09:23:00 AM**; Pending: **1**.
- `screenshots/phase1_test0_daily_rollover/phase1_test0_daily_rollover_after_rollover_check_20251204-092307.png`
- **Status:** Active Schedules: **Yes**; Next Notification: **tomorrow at 09:23:00 AM** (24 hours later); Pending: **1**.
---
## 6. Expected `dumpsys alarm` Shape
### 6.1. Representative Snippet
```text
RTC_WAKEUP #<N>: Alarm{<handle> type 0 origWhen <timestamp> whenElapsed ... com.timesafari.dailynotification}
tag=*walarm*:com.timesafari.daily.NOTIFICATION
type=RTC_WAKEUP origWhen=2025-12-05 09:23:00.000 ...
...
Next wake from idle: Alarm{<handle> type 0 origWhen <timestamp> ... com.timesafari.dailynotification}
tag=*walarm*:com.timesafari.daily.NOTIFICATION
```
### 6.2. Key Observations
- There should be **exactly one unique alarm handle** for the plugin (the handle will differ between runs).
- It can appear both in the main list and in **"Next wake from idle"**, but counted as **one** alarm (deduplication by alarm handle).
- `tag` must be `*walarm*:com.timesafari.daily.NOTIFICATION`.
- `type` must be `RTC_WAKEUP`.
- `origWhen` should be **tomorrow** at the same time-of-day as the scheduled notification (e.g., `2025-12-05 09:23:00.000` if scheduled for `2025-12-04 09:23:00.000`).
---
## 7. Expected `logcat` Patterns
### 7.1. Scheduling Test Notification
```text
DNP-SCHEDULE: Scheduling next daily alarm: id=daily_..., nextRun=2025-12-04 09:23:00, source=TEST_NOTIFICATION
DNP-NOTIFY: Stored notification content in database: id=daily_...
DNP-NOTIFY: Scheduling alarm: triggerTime=2025-12-04 09:23:00, ...
DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, ...
```
### 7.2. Rollover on Fire
```text
DNP-SCHEDULE: Scheduling next daily alarm: id=daily_rollover_..., nextRun=2025-12-05 09:23:00, source=ROLLOVER_ON_FIRE
DNP-NOTIFY: Stored notification content in database: id=notify_...
DNP-NOTIFY: Scheduling alarm: triggerTime=2025-12-05 09:23:00, ...
DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, ...
```
### 7.3. Critical Requirements
**Both sequences must be present** for a true PASS:
- `source=TEST_NOTIFICATION` sequence when scheduling the initial test notification
- `source=ROLLOVER_ON_FIRE` sequence when the notification fires and schedules tomorrow's alarm
- Times must match: initial schedule time → tomorrow's time (T + 24h)
- Example: `2025-12-04 09:23:00``2025-12-05 09:23:00`
---
## 8. Quick Pass/Fail Checklist
A run of TEST 0 is a **PASS** if all of the following are true:
### Script Output
- [ ] Shows "Found 1 notification alarm (expected: 1) preliminary check passed."
- [ ] Shows "Notification alarms after rollover: 1 (expected: 1)".
- [ ] Ends with "✅ TEST 0 PASSED: Daily rollover created exactly one NOTIFICATION alarm for tomorrow."
### UI State
- [ ] **Before scheduling:** Active Schedules: No; Next Notification: None scheduled.
- [ ] **After scheduling:** Active Schedules: Yes; Next Notification: *today* at the chosen time.
- [ ] **After rollover:** Active Schedules: Yes; Next Notification: *tomorrow* at the same time.
### `dumpsys alarm`
- [ ] Exactly one `RTC_WAKEUP` alarm with `tag=*walarm*:com.timesafari.daily.NOTIFICATION` for **tomorrow**.
- [ ] Same alarm handle may appear under "Next wake from idle", but no second distinct handle.
- [ ] `origWhen` timestamp is exactly 24 hours after the initial scheduled time.
### `logcat`
- [ ] Shows both `source=TEST_NOTIFICATION` and `source=ROLLOVER_ON_FIRE` sequences with matching times.
- [ ] No duplicate `DNP-SCHEDULE` entries for the same `nextRun` time.
- [ ] No errors or warnings related to alarm scheduling.
---
## 9. Notes / Deviations
### Failure Conditions
- If there is exactly **0** alarms after rollover, treat as **INCONCLUSIVE** and investigate:
- Check `logcat` for `ROLLOVER_ON_FIRE` sequence
- Verify `dumpsys alarm` manually
- Check for scheduling errors in logs
- If there are **>1** alarms after rollover, treat as **FAIL** (duplicate alarm bug):
- Check for multiple `DNP-SCHEDULE` entries with same `nextRun`
- Verify idempotence checks are working
- Check for race conditions between rollover and recovery paths
### Time-of-Day Variations
- Time-of-day may differ in future golden runs; **structure and relationships must remain the same**.
- The key is: initial time → tomorrow's time (T + 24h), not the specific hour/minute.
### Screenshot Timestamps
- Screenshot filenames include timestamps (`YYYYMMDD-HHMMSS`), so exact filenames will differ between runs.
- Focus on the **content** of screenshots (UI state) rather than exact filenames.
### Alarm Handle Variations
- The alarm handle (e.g., `Alarm{1f00a1b}`) will differ between runs; this is expected.
- The important thing is that there is **exactly one unique handle** per scheduled alarm.
---
## 10. Updating This Document
When updating this golden run document:
1. Update timestamps and IDs with actual values from your successful run
2. Replace placeholder values (marked with "Update...") with real data
3. Update screenshot filenames with actual timestamps
4. Add any environment-specific notes that might affect future runs
5. Document any deviations or edge cases encountered
**Last Golden Run Date:** 2025-12-04 (09:23:00 scheduled time)

View File

@@ -0,0 +1,327 @@
# Phase 1 — TEST 1 Golden Run (Force-Stop Recovery - Database Restoration)
**Related Docs:**
- [PHASE1_TEST0_GOLDEN.md](./PHASE1_TEST0_GOLDEN.md)
- [PHASE1_TEST1_GOLDEN.md](./PHASE1_TEST1_GOLDEN.md)
---
## 1. Test Overview
**Test Name:** TEST 1 — Force-Stop Recovery: Database Restoration
**Purpose:** Verify that after an Android **force-stop** (which clears all scheduled alarms), the plugin:
1. **Persists** the alarm schedule in its database.
2. **Detects** that alarms are missing on app launch.
3. **Rebuilds** the missing alarm(s) from the database.
4. Restores the **one-notification-per-day** contract:
- Before force-stop: 1 alarm
- After force-stop: 0 alarms
- After recovery: 1 alarm (same trigger time as before force-stop)
This golden run documents a **known-good execution** on 2025-12-04.
---
## 2. Environment & Build Info
- **Date/Time of Run:** 2025-12-04 (around 09:5109:55 UTC)
- **Command Used:**
```bash
./test-phase1.sh
```
* **Project:** `daily-notification-plugin` — `test-apps/android-test-app`
* **Gradle:**
* Build invoked via `./gradlew assembleDebug` (through the script)
* Gradle output included:
```text
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
```
* **APK Built:**
```text
./app/build/outputs/apk/debug/app-debug.apk
```
* **App ID:** `com.timesafari.dailynotification`
> **Note:** Device/emulator model & API level can be added here later if desired.
---
## 3. Step-by-Step Execution (Golden Run)
This section captures the **actual** sequence for the golden run.
1. Ran `./test-phase1.sh`.
2. Pre-flight checks:
* ✅ ADB device connected
* ✅ Emulator ready
3. Build phase:
* ✅ Debug APK built successfully.
* ✅ APK path: `./app/build/outputs/apk/debug/app-debug.apk`
4. Install phase:
* ✅ Previous app uninstall succeeded
* ✅ APK installed successfully
* ✅ App verified in package list
* ✅ Logcat cleared (twice)
5. **TEST 0** executed:
* Configured plugin in UI (if needed).
* Scheduled daily notification.
* Verified:
* Before scheduling: 0 plugin alarms.
* After scheduling: 1 plugin alarm for **today**.
* After fire/rollover: 1 plugin alarm for **tomorrow**.
* **TEST 0 PASSED.**
6. **TEST 1** started:
* Step 1: Detected 1 lingering plugin alarm (tomorrow's alarm from TEST 0).
* Auto-reset:
* Uninstalled app
* Reinstalled APK
* Cleared logcat
* Verified plugin alarms = 0
* Confirmed **clean starting state** for TEST 1.
7. Step 2: Launched app and confirmed plugin configured.
8. In UI: tapped **"Test Notification"** button (schedules a notification ~4 minutes in the future).
9. Step 3 (pre-FS verify):
* Verified **1** plugin alarm exists in AlarmManager.
* Confirmed alarm details (time, tag, type).
* Confirmed scheduling logs with `source=TEST_NOTIFICATION`.
10. Step 4: Performed **force-stop** via `adb shell am force-stop com.timesafari.dailynotification`.
11. Step 5 (post-FS verify):
* Verified plugin alarms = **0** (force-stop cleared alarms).
12. Step 6: Relaunched app (cold start).
13. Step 7 (recovery verify):
* Verified plugin alarms = **1** after recovery.
* Confirmed recovery logs under `DNP-REACTIVATION`:
* App launch recovery
* Boot recovery
* `rescheduled=1`
* Confirmed rescheduled alarm uses the **same scheduleId and triggerTime** as pre-force-stop.
14. Fire verification was **skipped** (`VERIFY_FIRE=false`).
15. TEST 1 summary:
* ✅ Before FS: 1 alarm
* ✅ After FS: 0 alarms
* ✅ After recovery: 1 alarm
* ✅ `rescheduled=1`, `errors=0`
* ✅ TEST 1 PASSED.
---
## 4. Expected Script Output (Key Excerpts)
These are the **critical excerpts** from the test harness output for a passing TEST 1.
### 4.1 Clean Start & Auto-Reset
```text
→ Step 1: Clean start - checking for lingering alarms...
Current plugin notification alarms: 1
System/other alarms: 17 (for context)
⚠️ Found 1 lingering plugin alarm(s) - these will interfere with TEST 1.
TEST 1 needs a clean state (no existing plugin alarms).
Resetting app state via uninstall + reinstall...
Uninstalling existing app...
✅ App uninstall succeeded (clean slate).
Reinstalling APK...
✅ App reinstall succeeded.
Clearing logcat buffer after reinstall...
Rechecking plugin alarms after reset...
Plugin alarms after reset: 0 (expected: 0)
✅ App state reset complete. TEST 1 starting from clean state.
```
### 4.2 Alarm Scheduled Before Force-Stop
```text
→ Step 3: Verifying alarm exists in AlarmManager (BEFORE force-stop)...
Plugin alarms: 1 (expected: 1)
System/other alarms: 19 (for context)
✅ ✅ Single plugin alarm confirmed in AlarmManager (one per day)
Alarm details:
RTC_WAKEUP #4: Alarm{161cd2b type 0 origWhen 1764842100000 whenElapsed 13009441 com.timesafari.dailynotification}
tag=*walarm*:com.timesafari.daily.NOTIFICATION
type=RTC_WAKEUP origWhen=2025-12-04 09:55:00.000 window=0 exactAllowReason=policy_permission repeatInterval=0 count=0 flags=0x3
policyWhenElapsed: requester=+3m4s281ms app_standby=-6s566ms device_idle=-- battery_saver=--
--
Alarm scheduled for: Thu Dec 4 09:55:00 AM UTC 2025 (1764842100000 ms)
Checking logs for scheduling confirmation...
12-04 09:51:49.150 6803 6867 W DNP-SCHEDULE: Cancelling existing alarm before rescheduling: requestCode=3454, scheduleId=daily_1764841909137, source=TEST_NOTIFICATION
12-04 09:51:49.151 6803 6867 I DNP-NOTIFY: Scheduling alarm: triggerTime=2025-12-04 09:55:00, delayMs=190849, requestCode=3454, scheduleId=daily_1764841909137
12-04 09:51:49.152 6803 6867 I DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, triggerTime=1764842100000, requestCode=3454, scheduleId=daily_1764841909137, source=TEST_NOTIFICATION, pendingIntentHash=267839060, showIntentHash=256236029
12-04 09:51:49.153 6803 6867 I DNP-NOTIFY: Alarm clock scheduled (setAlarmClock): triggerAt=1764842100000, requestCode=3454
```
### 4.3 Force-Stop & Alarm Clearance
```text
→ Step 4: Force-stopping app (clears all alarms)...
⚠️ Force-stop will clear ALL alarms from AlarmManager
Executing: adb shell am force-stop com.timesafari.dailynotification
Forcing stop of app process...
✅ Force stop issued
→ Step 5: Verifying alarms are MISSING from AlarmManager (AFTER force-stop)...
Plugin alarms after force-stop: 0 (expected: 0)
System/other alarms: 17 (for context)
✅ ✅ Plugin alarms cleared by force-stop (count: 0)
This confirms: Force-stop cleared alarms from AlarmManager
```
### 4.4 Recovery & Rescheduling
```text
→ Step 6: Relaunching app (triggers recovery from database)...
Clearing logcat buffer...
✅ Logs cleared
Launching app...
Starting: Intent { cmp=com.timesafari.dailynotification/.MainActivity }
✅ App launched
→ Step 7: Verifying recovery rebuilt alarms from database...
Plugin alarms after recovery: 1 (expected: 1)
System/other alarms: 21 (for context)
Checking recovery logs...
12-04 09:52:21.919 6954 7015 I DNP-REACTIVATION: Starting app launch recovery (Phase 1: cold start only)
12-04 09:52:21.926 6954 7015 I DNP-REACTIVATION: Cold start recovery: checking for missed notifications
12-04 09:52:21.937 6954 7015 I DNP-REACTIVATION: Rescheduled alarm: daily_1764841909137 for 1764842100000
12-04 09:52:21.937 6954 7015 I DNP-REACTIVATION: Rescheduled missing alarm: daily_1764841909137 at 1764842100000
12-04 09:52:21.952 6954 7015 I DNP-REACTIVATION: Cold start recovery complete: missed=0, rescheduled=1, verified=0, errors=0
12-04 09:52:21.952 6954 7015 I DNP-REACTIVATION: App launch recovery completed: missed=0, rescheduled=1, verified=0, errors=0
12-04 09:52:22.077 6954 7015 I DNP-REACTIVATION: Starting boot recovery
12-04 09:52:22.135 6954 7017 I DNP-REACTIVATION: Loaded 1 schedules from DB
12-04 09:52:22.138 6954 7017 I DNP-REACTIVATION: Rescheduled alarm: daily_1764841909137 for 1764842100000
12-04 09:52:22.143 6954 7017 I DNP-REACTIVATION: Boot recovery complete: missed=0, rescheduled=1, verified=0, errors=0
```
Final summary:
```text
✅ ✅ Alarms restored in AlarmManager (count: 1)
✅ ✅ Recovery logs confirm rescheduling (rescheduled=1)
✅ TEST 1 PASSED: Recovery successfully rebuilt alarms from database!
Summary:
- Before force-stop: 1 alarm(s)
- After force-stop: 0 alarm(s) (cleared)
- After recovery: 1 alarm(s) (rebuilt)
- Rescheduled: 1 alarm(s)
- Verified: 0 alarm(s)
Skipping fire verification (VERIFY_FIRE=false, set VERIFY_FIRE=true to enable)
```
---
## 5. Expected `dumpsys alarm` Shapes
### 5.1 Before Force-Stop (Scheduled)
* **Plugin alarm count:** 1
* Example block (shape, not necessarily exact handle):
```text
RTC_WAKEUP #4: Alarm{161cd2b type 0 origWhen 1764842100000 whenElapsed 13009441 com.timesafari.dailynotification}
tag=*walarm*:com.timesafari.daily.NOTIFICATION
type=RTC_WAKEUP origWhen=2025-12-04 09:55:00.000 window=0 exactAllowReason=policy_permission repeatInterval=0 count=0 flags=0x3
policyWhenElapsed: requester=+3m4s281ms app_standby=-6s566ms device_idle=-- battery_saver=--
```
### 5.2 After Force-Stop
* **Plugin alarm count:** 0
* No `*walarm*:com.timesafari.daily.NOTIFICATION` entries should appear.
### 5.3 After Recovery
* **Plugin alarm count:** 1
* Alarm should be restored with:
* Same `scheduleId`: `daily_1764841909137`
* Same `triggerTime`: `1764842100000` → `2025-12-04 09:55:00`
---
## 6. Expected `logcat` Patterns
For a passing run, look for:
* **Scheduling (before FS):**
```text
DNP-NOTIFY: Scheduling alarm: triggerTime=2025-12-04 09:55:00, delayMs=..., requestCode=3454, scheduleId=daily_1764841909137
DNP-SCHEDULE: Scheduling OS alarm: variant=ALARM_CLOCK, action=com.timesafari.daily.NOTIFICATION, triggerTime=1764842100000, requestCode=3454, scheduleId=daily_1764841909137, source=TEST_NOTIFICATION, ...
```
* **Recovery (after FS + relaunch):**
```text
DNP-REACTIVATION: Starting app launch recovery (Phase 1: cold start only)
DNP-REACTIVATION: Rescheduled alarm: daily_1764841909137 for 1764842100000
DNP-REACTIVATION: Cold start recovery complete: missed=0, rescheduled=1, verified=0, errors=0
DNP-REACTIVATION: App launch recovery completed: missed=0, rescheduled=1, verified=0, errors=0
DNP-REACTIVATION: Starting boot recovery
DNP-REACTIVATION: Loaded 1 schedules from DB
DNP-REACTIVATION: Rescheduled alarm: daily_1764841909137 for 1764842100000
DNP-REACTIVATION: Boot recovery complete: missed=0, rescheduled=1, verified=0, errors=0
```
Key invariants:
* `rescheduled=1`
* `errors=0`
* `scheduleId` and `triggerTime` match pre-FS values.
---
## 7. Quick Pass/Fail Checklist
A TEST 1 run is a **PASS** if all of the following are true:
* **Starting state:**
* Script auto-resets if lingering alarms exist from TEST 0.
* After reset: plugin alarm count = **0**.
* **Pre-force-stop:**
* Plugin alarm count = **1**.
* Alarm is tagged `*walarm*:com.timesafari.daily.NOTIFICATION`.
* `triggerTime` and `origWhen` are consistent (e.g. `2025-12-04 09:55:00`, `1764842100000`).
* `scheduleId` looks like `daily_<timestamp>` (here: `daily_1764841909137`).
* Logs show `source=TEST_NOTIFICATION`.
* **After force-stop:**
* Plugin alarm count = **0**.
* No NOTIFICATION alarms remain in `dumpsys`.
* **After recovery (relaunch):**
* Plugin alarm count = **1**.
* Logs show `DNP-REACTIVATION` with:
* `rescheduled=1`
* `errors=0`
* Same `scheduleId` and `triggerTime` as pre-FS.
* **Script summary:**
* States:
* Before force-stop: 1 alarm
* After force-stop: 0 alarms
* After recovery: 1 alarm
* Ends with: `✅ TEST 1 PASSED: Recovery successfully rebuilt alarms from database!`
If any of these conditions fail, the run is **NOT GOLDEN** and should not overwrite this reference.
---
## 8. Notes / Deviations
* This golden run **skips fire verification** (`VERIFY_FIRE=false`).
Future runs may enable fire verification; if that becomes standard, update this doc.
* It is acceptable for both:
* **Cold start recovery** and
* **Boot recovery**
to run on app launch, as long as:
* `rescheduled=1`
* No duplicate alarms are scheduled.
* If OS behavior changes (e.g., force-stop no longer clears alarms on a future Android version), this test's expectations may need to be revised; document any such deviations explicitly here before changing the checklist.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,352 @@
#!/usr/bin/env bash
set -euo pipefail
# ========================================
# Phase 2 Testing Script Force Stop Recovery
# ========================================
# Source shared library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/alarm-test-lib.sh"
# Phase 2 specific configuration
# Log tags / patterns (matched to actual ReactivationManager logs)
FORCE_STOP_SCENARIO_VALUE="FORCE_STOP"
COLD_START_SCENARIO_VALUE="COLD_START"
NONE_SCENARIO_VALUE="NONE"
BOOT_SCENARIO_VALUE="BOOT"
# Allow selecting specific tests on the command line (e.g. ./test-phase2.sh 2 3)
SELECTED_TESTS=()
# ------------------------------------------------------------------------------
# TEST 1 Force Stop with Cleared Alarms
# ------------------------------------------------------------------------------
test1_force_stop_cleared_alarms() {
section "TEST 1: Force Stop Alarms Cleared"
echo "Purpose: Verify force stop detection and alarm rescheduling when alarms are cleared."
pause
substep "Step 1: Launch app & check plugin status"
launch_app
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf either shows ❌ or 'Not configured', click 'Configure Plugin', wait until both are ✅, then press Enter."
ui_prompt "Now schedule at least one future notification (e.g., click 'Test Notification' to schedule for a few minutes in the future)."
substep "Step 2: Verify alarms are scheduled"
show_alarms
local before_count system_count
before_count="$(get_plugin_alarm_count)"
system_count="$(get_system_alarm_count)"
info "Plugin alarms before force stop: $before_count (expected: 1)"
info "System/other alarms: $system_count (for context)"
if [[ "$before_count" -eq 0 ]]; then
warn "No plugin alarms found before force stop; TEST 1 may not be meaningful."
elif [[ "$before_count" -eq 1 ]]; then
ok "Single plugin alarm confirmed (one per day)"
else
warn "Found $before_count plugin alarms (expected: 1)"
fi
pause
substep "Step 3: Force stop app (should clear alarms on many devices)"
force_stop_app
substep "Step 4: Check alarms after force stop"
local after_count system_after
after_count="$(get_plugin_alarm_count)"
system_after="$(get_system_alarm_count)"
info "Plugin alarms after force stop: $after_count (expected: 0)"
info "System/other alarms: $system_after (for context)"
show_alarms
if [[ "$after_count" -gt 0 ]]; then
warn "Plugin alarms still present after force stop. This device/OS may not clear alarms on force stop."
warn "TEST 1 will continue but may not fully validate FORCE_STOP scenario."
fi
pause
substep "Step 4.5: Clear boot flag (prevent false BOOT detection)"
# Clear boot flag to ensure force stop detection works correctly
# Boot flag might be set from previous runs or emulator quirks
adb shell "run-as ${APP_ID} rm -f shared_prefs/dailynotification_recovery.xml 2>/dev/null || true"
info "Boot flag cleared (if it existed)"
substep "Step 5: Launch app (triggers recovery) and capture logs"
clear_logs
launch_app
sleep 5 # give recovery a moment to run
info "Collecting recovery logs..."
local logs
logs="$(get_recovery_logs)"
echo "$logs"
local scenario rescheduled verified errors
scenario="$(extract_scenario_from_logs "$logs")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
verified="$(extract_field_from_logs "$logs" "verified")"
errors="$(extract_field_from_logs "$logs" "errors")"
echo
info "Parsed recovery summary:"
echo " scenario = ${scenario:-<none>}"
echo " rescheduled= ${rescheduled}"
echo " verified = ${verified}"
echo " errors = ${errors}"
echo
if [[ "$errors" -gt 0 ]]; then
error "Recovery reported errors>0 (errors=$errors)"
fi
if [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -gt 0 ]]; then
ok "TEST 1 PASSED: Force stop detected and alarms rescheduled (scenario=$scenario, rescheduled=$rescheduled)."
elif [[ "$scenario" == "$FORCE_STOP_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
warn "TEST 1: scenario=FORCE_STOP but rescheduled=0. Check implementation or logs."
elif [[ "$after_count" -gt 0 ]]; then
info "TEST 1: Device/emulator kept alarms after force stop; FORCE_STOP scenario may not trigger here."
if [[ "$rescheduled" -gt 0 ]]; then
info "Recovery still worked (rescheduled=$rescheduled), but scenario was ${scenario:-COLD_START} instead of FORCE_STOP"
fi
else
warn "TEST 1: Expected FORCE_STOP scenario not clearly detected. Review logs and scenario detection logic."
info "Scenario detected: ${scenario:-<none>}, rescheduled=$rescheduled"
fi
substep "Step 6: Verify alarms are rescheduled in AlarmManager"
show_alarms
}
# ------------------------------------------------------------------------------
# TEST 2 Force Stop / Process Stop with Intact Alarms
# ------------------------------------------------------------------------------
test2_force_stop_intact_alarms() {
section "TEST 2: Force Stop / Process Stop Alarms Intact"
echo "Purpose: Verify that heavy FORCE_STOP recovery does not run when alarms are still present."
pause
substep "Step 1: Launch app & schedule notifications"
launch_app
ui_prompt "In the app UI, ensure plugin is configured and schedule at least one future notification.\n\nPress Enter when done."
substep "Step 2: Verify alarms are scheduled"
show_alarms
local before system_before
before="$(get_plugin_alarm_count)"
system_before="$(get_system_alarm_count)"
info "Plugin alarms before stop: $before (expected: 1)"
info "System/other alarms: $system_before (for context)"
if [[ "$before" -eq 0 ]]; then
warn "No plugin alarms found; TEST 2 may not be meaningful."
elif [[ "$before" -eq 1 ]]; then
ok "Single plugin alarm confirmed (one per day)"
else
warn "Found $before plugin alarms (expected: 1)"
fi
pause
substep "Step 3: Simulate a 'soft' stop or process kill that does NOT clear alarms"
info "Killing app process (non-destructive - may not clear alarms)..."
$ADB_BIN shell am kill "$APP_ID" || true
sleep 2
ok "Kill signal sent (soft stop)"
substep "Step 4: Verify alarms are still scheduled"
local after system_after
after="$(get_plugin_alarm_count)"
system_after="$(get_system_alarm_count)"
info "Plugin alarms after soft stop: $after (expected: 1)"
info "System/other alarms: $system_after (for context)"
show_alarms
if [[ "$after" -eq 0 ]]; then
warn "Alarms appear cleared after soft stop; this environment may not distinguish TEST 2 well."
fi
pause
substep "Step 5: Relaunch app and check recovery logs"
clear_logs
launch_app
sleep 5
info "Collecting recovery logs..."
local logs
logs="$(get_recovery_logs)"
echo "$logs"
local scenario rescheduled missed verified errors
scenario="$(extract_scenario_from_logs "$logs")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
missed="$(extract_field_from_logs "$logs" "missed")"
verified="$(extract_field_from_logs "$logs" "verified")"
errors="$(extract_field_from_logs "$logs" "errors")"
echo
info "Parsed recovery summary:"
echo " scenario = ${scenario:-<none>}"
echo " rescheduled= ${rescheduled}"
echo " missed = ${missed}"
echo " verified = ${verified}"
echo " errors = ${errors}"
echo
if [[ "$errors" -gt 0 ]]; then
error "Recovery reported errors>0 (errors=$errors)"
fi
if [[ "$after" -gt 0 && "$rescheduled" -eq 0 && "$scenario" != "$FORCE_STOP_SCENARIO_VALUE" ]]; then
ok "TEST 2 PASSED: Alarms remained intact, and FORCE_STOP scenario did not run (scenario=$scenario, rescheduled=0)."
else
warn "TEST 2: Verify that FORCE_STOP recovery didn't misfire when alarms were intact."
info "Scenario=${scenario:-<none>}, rescheduled=$rescheduled, after_count=$after"
fi
}
# ------------------------------------------------------------------------------
# TEST 3 First Launch / Empty DB Safeguard
# ------------------------------------------------------------------------------
test3_first_launch_no_schedules() {
section "TEST 3: First Launch / No Schedules Safeguard"
echo "Purpose: Ensure force-stop recovery is NOT triggered when DB is empty or plugin isn't configured."
pause
substep "Step 1: Uninstall app to clear DB/state"
set +e
$ADB_BIN uninstall "$APP_ID" >/dev/null 2>&1
set -e
ok "App uninstalled (state cleared)"
substep "Step 2: Reinstall app"
if $ADB_BIN install -r "$APK_PATH"; then
ok "App installed"
else
error "Reinstall failed"
exit 1
fi
info "Clearing logcat..."
$ADB_BIN logcat -c
ok "Logs cleared"
pause
substep "Step 3: Launch app for the first time"
launch_app
sleep 5
substep "Step 4: Collect logs and ensure no force-stop recovery ran"
local logs
logs="$(get_recovery_logs)"
echo "$logs"
local scenario rescheduled
scenario="$(extract_scenario_from_logs "$logs")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
echo
info "Parsed summary:"
echo " scenario = ${scenario:-<none>}"
echo " rescheduled= ${rescheduled}"
echo
if [[ -z "$logs" ]]; then
ok "TEST 3 PASSED: No force-stop recovery logs on first launch."
elif [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
ok "TEST 3 PASSED: NONE scenario logged with rescheduled=0 on first launch."
elif [[ "$rescheduled" -gt 0 ]]; then
warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that force-stop recovery isn't misfiring."
else
info "TEST 3: Logs present but no rescheduling; review scenario handling to ensure it's explicit about NONE / FIRST_LAUNCH."
fi
}
# ------------------------------------------------------------------------------
# Main
# ------------------------------------------------------------------------------
main() {
# Allow selecting specific tests: e.g. `./test-phase2.sh 1 3`
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) 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"
return 0
fi
SELECTED_TESTS=("$@")
echo
echo "========================================"
echo "Phase 2 Testing Script Force Stop Recovery"
echo "========================================"
echo
echo "This script will guide you through Phase 2 tests."
echo "You'll be prompted when UI interaction is needed."
echo
pause
require_adb_device
build_app
install_app
if should_run_test "1" SELECTED_TESTS; then
test1_force_stop_cleared_alarms
pause
fi
if should_run_test "2" SELECTED_TESTS; then
test2_force_stop_intact_alarms
pause
fi
if should_run_test "3" SELECTED_TESTS; then
test3_first_launch_no_schedules
fi
section "Testing Complete"
echo "Test Results Summary (see logs above for details):"
echo
echo "TEST 1: Force Stop Alarms Cleared"
echo " - Check logs for scenario=$FORCE_STOP_SCENARIO_VALUE and rescheduled>0"
echo
echo "TEST 2: Force Stop / Process Stop Alarms Intact"
echo " - Verify FORCE_STOP scenario is not incorrectly triggered when alarms are still present"
echo
echo "TEST 3: First Launch / No Schedules"
echo " - Confirm that no force-stop recovery runs, or that NONE/FIRST_LAUNCH scenario is logged with rescheduled=0"
echo
ok "Phase 2 testing script complete!"
echo
echo "Next steps:"
echo " - Review logs above"
echo " - Capture snippets into PHASE2-EMULATOR-TESTING.md"
echo " - Update PHASE2-VERIFICATION.md and unified directive status matrix"
echo
}
main "$@"

View File

@@ -0,0 +1,437 @@
#!/usr/bin/env bash
set -euo pipefail
# ========================================
# Phase 3 Testing Script Boot Recovery
# ========================================
# Source shared library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/alarm-test-lib.sh"
# Phase 3 specific configuration
# Log tags / patterns (matched to actual ReactivationManager logs)
BOOT_SCENARIO_VALUE="BOOT"
NONE_SCENARIO_VALUE="NONE"
# Allow selecting specific tests on the command line (e.g. ./test-phase3.sh 1 3)
SELECTED_TESTS=()
# Phase 3 specific: override extract_scenario_from_logs to handle boot recovery
extract_scenario_from_logs() {
local logs="$1"
local scen
# Looks for "Detected scenario: BOOT" or "Starting boot recovery" format
if echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
echo "$BOOT_SCENARIO_VALUE"
else
# Use shared library function as fallback
scen="$(grep -oE "${SCENARIO_KEY}[A-Z_]+" <<<"$logs" | tail -n1 | sed "s/${SCENARIO_KEY}//" || true)"
echo "$scen"
fi
}
# ------------------------------------------------------------------------------
# TEST 1 Boot with Future Alarms
# ------------------------------------------------------------------------------
test1_boot_future_alarms() {
section "TEST 1: Boot with Future Alarms"
echo "Purpose: Verify alarms are recreated on boot when schedules have future run times."
pause
substep "Step 1: Launch app & check plugin status"
launch_app
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf either shows ❌ or 'Not configured', click 'Configure Plugin', wait until both are ✅, then press Enter."
ui_prompt "Now schedule at least one future notification (e.g., click 'Test Notification' to schedule for a few minutes in the future)."
substep "Step 2: Verify alarms are scheduled"
show_alarms
local before_count system_before
before_count="$(get_plugin_alarm_count)"
system_before="$(get_system_alarm_count)"
info "Plugin alarms before reboot: $before_count (expected: 1)"
info "System/other alarms: $system_before (for context)"
if [[ "$before_count" -eq 0 ]]; then
warn "No plugin alarms found before reboot; TEST 1 may not be meaningful."
elif [[ "$before_count" -eq 1 ]]; then
ok "Single plugin alarm confirmed (one per day)"
else
warn "Found $before_count plugin alarms (expected: 1)"
fi
pause
substep "Step 3: Reboot emulator"
warn "The emulator will reboot now. This will take 30-60 seconds."
pause
reboot_emulator
substep "Step 4: Collect boot recovery logs"
info "Collecting recovery logs from boot..."
sleep 2 # Give recovery a moment to complete
local logs
logs="$(get_recovery_logs)"
echo "$logs"
local missed rescheduled verified errors scenario
missed="$(extract_field_from_logs "$logs" "missed")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
verified="$(extract_field_from_logs "$logs" "verified")"
errors="$(extract_field_from_logs "$logs" "errors")"
scenario="$(extract_scenario_from_logs "$logs")"
echo
info "Parsed recovery summary:"
echo " scenario = ${scenario:-<none>}"
echo " missed = ${missed}"
echo " rescheduled= ${rescheduled}"
echo " verified = ${verified}"
echo " errors = ${errors}"
echo
if [[ "$errors" -gt 0 ]]; then
error "Recovery reported errors>0 (errors=$errors)"
fi
substep "Step 5: Verify alarms were recreated"
show_alarms
local after_count system_after
after_count="$(get_plugin_alarm_count)"
system_after="$(get_system_alarm_count)"
info "Plugin alarms after boot: $after_count (expected: 1)"
info "System/other alarms: $system_after (for context)"
if [[ "$scenario" == "$BOOT_SCENARIO_VALUE" && "$rescheduled" -gt 0 ]]; then
ok "TEST 1 PASSED: Boot recovery detected and alarms rescheduled (scenario=$scenario, rescheduled=$rescheduled)."
elif echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
if [[ "$rescheduled" -gt 0 ]]; then
ok "TEST 1 PASSED: Boot recovery ran and alarms rescheduled (rescheduled=$rescheduled)."
else
warn "TEST 1: Boot recovery ran but rescheduled=0. Check implementation or logs."
fi
else
warn "TEST 1: Boot recovery not clearly detected. Review logs and boot receiver implementation."
info "Scenario detected: ${scenario:-<none>}, rescheduled=$rescheduled"
fi
}
# ------------------------------------------------------------------------------
# TEST 2 Boot with Past Alarms
# ------------------------------------------------------------------------------
test2_boot_past_alarms() {
section "TEST 2: Boot with Past Alarms"
echo "Purpose: Verify missed alarms are detected and next occurrence is scheduled on boot."
pause
substep "Step 1: Launch app & ensure plugin configured"
launch_app
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf needed, click 'Configure Plugin', then press Enter."
ui_prompt "Click 'Test Notification' to schedule a notification for 2 minutes in the future.\n\nAfter scheduling, we'll wait for the alarm time to pass, then reboot."
substep "Step 2: Wait for alarm time to pass"
info "Waiting 3 minutes for scheduled alarm time to pass..."
warn "You can manually advance system time if needed (requires root/emulator)"
sleep 180 # Wait 3 minutes
substep "Step 3: Verify alarm time has passed"
info "Alarm time should now be in the past"
show_alarms
pause
substep "Step 4: Reboot emulator"
warn "The emulator will reboot now. This will take 30-60 seconds."
pause
reboot_emulator
substep "Step 5: Collect boot recovery logs"
info "Collecting recovery logs from boot..."
sleep 2
local logs
logs="$(get_recovery_logs)"
echo "$logs"
local missed rescheduled verified errors scenario
# For TEST 2, we need the FIRST entry (which has missed count) not the last
# Boot recovery runs twice (LOCKED_BOOT_COMPLETED and BOOT_COMPLETED)
# First run marks missed alarms, second run only reschedules (missed=0)
missed="$(echo "$logs" | grep -E "missed=" | sed -E "s/.*missed=([0-9]+).*/\1/" | head -n1 || echo "0")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
verified="$(extract_field_from_logs "$logs" "verified")"
errors="$(extract_field_from_logs "$logs" "errors")"
scenario="$(extract_scenario_from_logs "$logs")"
echo
info "Parsed recovery summary:"
echo " scenario = ${scenario:-<none>}"
echo " missed = ${missed}"
echo " rescheduled= ${rescheduled}"
echo " verified = ${verified}"
echo " errors = ${errors}"
echo
if [[ "$errors" -gt 0 ]]; then
error "Recovery reported errors>0 (errors=$errors)"
fi
if [[ "$missed" -ge 1 && "$rescheduled" -ge 1 ]]; then
ok "TEST 2 PASSED: Past alarms detected and next occurrence scheduled (missed=$missed, rescheduled=$rescheduled)."
elif [[ "$missed" -ge 1 ]]; then
warn "TEST 2: Past alarms detected (missed=$missed) but rescheduled=$rescheduled. Check reschedule logic."
else
warn "TEST 2: No missed alarms detected. Verify alarm time actually passed before reboot."
fi
}
# ------------------------------------------------------------------------------
# TEST 3 Boot with No Schedules
# ------------------------------------------------------------------------------
test3_boot_no_schedules() {
section "TEST 3: Boot with No Schedules"
echo "Purpose: Verify boot recovery handles empty database gracefully."
pause
substep "Step 1: Uninstall app to clear DB/state"
set +e
$ADB_BIN uninstall "$APP_ID" >/dev/null 2>&1
set -e
ok "App uninstalled (state cleared)"
substep "Step 2: Reinstall app"
if $ADB_BIN install -r "$APK_PATH"; then
ok "App installed"
else
error "Reinstall failed"
exit 1
fi
info "Clearing logcat..."
$ADB_BIN logcat -c
ok "Logs cleared"
pause
substep "Step 3: Reboot emulator WITHOUT scheduling anything"
warn "Do NOT schedule any notifications. The app should have no schedules in the database."
warn "The emulator will reboot now. This will take 30-60 seconds."
pause
reboot_emulator
substep "Step 4: Collect boot recovery logs"
info "Collecting recovery logs from boot..."
sleep 2
local logs
logs="$(get_recovery_logs)"
echo "$logs"
local scenario rescheduled missed
scenario="$(extract_scenario_from_logs "$logs")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
missed="$(extract_field_from_logs "$logs" "missed")"
echo
info "Parsed recovery summary:"
echo " scenario = ${scenario:-<none>}"
echo " rescheduled= ${rescheduled}"
echo " missed = ${missed}"
echo
if [[ -z "$logs" ]]; then
ok "TEST 3 PASSED: No recovery logs when there are no schedules (safe behavior)."
return
fi
if echo "$logs" | grep -qiE "No schedules found|No schedules present"; then
ok "TEST 3 PASSED: Explicit 'No schedules found' message logged with no rescheduling."
elif [[ "$scenario" == "$NONE_SCENARIO_VALUE" && "$rescheduled" -eq 0 ]]; then
ok "TEST 3 PASSED: NONE scenario detected with no rescheduling."
elif [[ "$rescheduled" -gt 0 ]]; then
warn "TEST 3: rescheduled>0 on first launch / empty DB. Check that boot recovery isn't misfiring."
else
info "TEST 3: Logs present but no rescheduling; review scenario handling to ensure it's explicit about NONE / NO_SCHEDULES."
fi
}
# ------------------------------------------------------------------------------
# TEST 4 Silent Boot Recovery (App Never Opened)
# ------------------------------------------------------------------------------
test4_silent_boot_recovery() {
section "TEST 4: Silent Boot Recovery (App Never Opened)"
echo "Purpose: Verify boot recovery occurs even when the app is never opened after reboot."
pause
substep "Step 1: Launch app & ensure plugin configured"
launch_app
ui_prompt "In the app UI, verify plugin status:\n\n ⚙️ Plugin Settings: ✅ Configured\n 🔌 Native Fetcher: ✅ Configured\n\nIf needed, click 'Configure Plugin', then press Enter."
ui_prompt "Click 'Test Notification' to schedule a notification for a few minutes in the future."
substep "Step 2: Verify alarms are scheduled"
show_alarms
local before_count system_before
before_count="$(get_plugin_alarm_count)"
system_before="$(get_system_alarm_count)"
info "Plugin alarms before reboot: $before_count (expected: 1)"
info "System/other alarms: $system_before (for context)"
if [[ "$before_count" -eq 0 ]]; then
warn "No plugin alarms found; TEST 4 may not be meaningful."
elif [[ "$before_count" -eq 1 ]]; then
ok "Single plugin alarm confirmed (one per day)"
else
warn "Found $before_count plugin alarms (expected: 1)"
fi
pause
substep "Step 3: Reboot emulator (DO NOT open app after reboot)"
warn "IMPORTANT: After reboot, DO NOT open the app. Boot recovery should run silently."
warn "The emulator will reboot now. This will take 30-60 seconds."
pause
reboot_emulator
substep "Step 4: Collect boot recovery logs (without opening app)"
info "Collecting recovery logs from boot (app was NOT opened)..."
sleep 2
local logs
logs="$(get_recovery_logs)"
echo "$logs"
local missed rescheduled verified errors scenario
missed="$(extract_field_from_logs "$logs" "missed")"
rescheduled="$(extract_field_from_logs "$logs" "rescheduled")"
verified="$(extract_field_from_logs "$logs" "verified")"
errors="$(extract_field_from_logs "$logs" "errors")"
scenario="$(extract_scenario_from_logs "$logs")"
echo
info "Parsed recovery summary:"
echo " scenario = ${scenario:-<none>}"
echo " missed = ${missed}"
echo " rescheduled= ${rescheduled}"
echo " verified = ${verified}"
echo " errors = ${errors}"
echo
substep "Step 5: Verify alarms were recreated (without opening app)"
show_alarms
local after_count system_after
after_count="$(get_plugin_alarm_count)"
system_after="$(get_system_alarm_count)"
info "Plugin alarms after boot (app never opened): $after_count (expected: 1)"
info "System/other alarms: $system_after (for context)"
if [[ "$after_count" -gt 0 && "$rescheduled" -gt 0 ]]; then
ok "TEST 4 PASSED: Boot recovery occurred silently and alarms were recreated (rescheduled=$rescheduled) without app launch."
elif [[ "$rescheduled" -gt 0 ]]; then
ok "TEST 4 PASSED: Boot recovery occurred silently (rescheduled=$rescheduled), but alarm count check unclear."
elif echo "$logs" | grep -qi "Starting boot recovery\|boot recovery"; then
warn "TEST 4: Boot recovery ran but alarms may not have been recreated. Check logs and implementation."
else
warn "TEST 4: Boot recovery not detected. Verify boot receiver is registered and has BOOT_COMPLETED permission."
fi
}
# ------------------------------------------------------------------------------
# Main
# ------------------------------------------------------------------------------
main() {
# Allow selecting specific tests: e.g. `./test-phase3.sh 1 3`
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 (silent boot recovery)"
return 0
fi
SELECTED_TESTS=("$@")
echo
echo "========================================"
echo "Phase 3 Testing Script Boot Recovery"
echo "========================================"
echo
echo "This script will guide you through Phase 3 tests."
echo "You'll be prompted when UI interaction is needed."
echo
echo "⚠️ WARNING: This script will reboot the emulator multiple times."
echo " Each reboot takes 30-60 seconds."
echo
pause
require_adb_device
build_app
install_app
if should_run_test "1" SELECTED_TESTS; then
test1_boot_future_alarms
pause
fi
if should_run_test "2" SELECTED_TESTS; then
test2_boot_past_alarms
pause
fi
if should_run_test "3" SELECTED_TESTS; then
test3_boot_no_schedules
pause
fi
if should_run_test "4" SELECTED_TESTS; then
test4_silent_boot_recovery
fi
section "Testing Complete"
echo "Test Results Summary (see logs above for details):"
echo
echo "TEST 1: Boot with Future Alarms"
echo " - Check logs for scenario=$BOOT_SCENARIO_VALUE and rescheduled>0"
echo
echo "TEST 2: Boot with Past Alarms"
echo " - Check that missed>=1 and rescheduled>=1"
echo
echo "TEST 3: Boot with No Schedules"
echo " - Check that no recovery runs, or NONE scenario is logged with rescheduled=0"
echo
echo "TEST 4: Silent Boot Recovery"
echo " - Check that boot recovery occurred and alarms were recreated without app launch"
echo
ok "Phase 3 testing script complete!"
echo
echo "Next steps:"
echo " - Review logs above"
echo " - Capture snippets into PHASE3-EMULATOR-TESTING.md"
echo " - Update PHASE3-VERIFICATION.md and unified directive status matrix"
echo
}
main "$@"

View File

@@ -103,3 +103,91 @@ npm run build
```sh
npm run lint
```
## ADB Commands for Android Testing
**Package Name**: `com.timesafari.dailynotification.test`
### Check Device Connection
```sh
adb devices
```
### Install App
Install the debug APK to a connected device or emulator:
```sh
# From project root
cd android
./gradlew installDebug
# Or from test-apps/daily-notification-test directory
cd android && ./gradlew installDebug
```
### List Installed Packages
Check if the app is installed:
```sh
# List all packages (filter for this app)
adb shell pm list packages | grep timesafari
# List only this app's package
adb shell pm list packages com.timesafari.dailynotification.test
```
### Launch/Raise the App
Launch the app or bring it to foreground:
```sh
# Launch the main activity
adb shell am start -n com.timesafari.dailynotification.test/.MainActivity
# Launch with explicit intent
adb shell am start -a android.intent.action.MAIN -n com.timesafari.dailynotification.test/.MainActivity
```
### Uninstall App
Remove the app from the device:
```sh
# Uninstall by package name
adb uninstall com.timesafari.dailynotification.test
# Force uninstall (if regular uninstall fails)
adb shell pm uninstall -k --user 0 com.timesafari.dailynotification.test
```
### Additional Useful ADB Commands
**View App Logs**:
```sh
# Filter logs for this app
adb logcat | grep -E "timesafari|DailyNotification|DNP"
# Clear logs before testing
adb logcat -c
```
**Check App Info**:
```sh
# Get app version and info
adb shell dumpsys package com.timesafari.dailynotification.test | grep -A 5 "versionName\|versionCode"
```
**Force Stop App**:
```sh
# Force stop the app (useful for testing recovery scenarios)
adb shell am force-stop com.timesafari.dailynotification.test
```
**Clear App Data**:
```sh
# Clear app data (resets to fresh install state)
adb shell pm clear com.timesafari.dailynotification.test
```

View File

@@ -117,7 +117,6 @@
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@@ -417,7 +416,6 @@
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/types": "^7.28.4"
},
@@ -636,7 +634,6 @@
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz",
"integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -2068,7 +2065,6 @@
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/types": "8.46.1",
@@ -2667,7 +2663,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2916,7 +2911,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
@@ -3379,7 +3373,6 @@
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3441,7 +3434,6 @@
"integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"natural-compare": "^1.4.0",
@@ -4301,7 +4293,6 @@
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -5730,7 +5721,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -5808,7 +5798,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6042,7 +6031,6 @@
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -6313,7 +6301,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -6333,7 +6320,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.22",
"@vue/compiler-sfc": "3.5.22",