Add local-android-testing-ngrok.md for FCM wakeup, debug panel, Firebase setup, platform/battery notes, troubleshooting, verification checklist, and end-to-end QA flow. Add local-android-testing-analysis.md as planning notes mapping reuse from the iOS ngrok guide.
19 KiB
Android Local Notification Testing — Planning Analysis
Created: 2026-06-02
Source document: local-ios-testing-ngrok.md
Purpose: Plan a future Android counterpart guide by mapping what can be reused from the iOS ngrok workflow and what must be written for Android-specific push, permissions, and OS behavior.
Status: Planning only — does not replace or modify the iOS guide.
Executive summary
The iOS guide’s backend + ngrok + in-app debug panel path is platform-agnostic. Most of sections 1–3, 6, 9 (with log tooling swapped), 10 (with platform: "android"), 12, and parts of 11 can be copied or lightly edited.
Everything involving APNs, Xcode, Apple Developer, iOS capabilities, and iOS background/silent-push caveats must be replaced. Android adds direct FCM delivery (no APNs hop), google-services.json, runtime notification permissions (API 33+), Doze / battery optimization / OEM restrictions, and different force-stop / background semantics.
Existing related docs to cross-link (not duplicate):
- android-physical-device-guide.md — USB,
adb, build/run commands - notification-system-overview.md
- notification-from-api-call.md
- notification-permissions-and-rollovers.md
iOS guide structure (reference map)
| § | iOS doc heading | Reuse for Android |
|---|---|---|
| Intro | Architecture overview | Adapt — swap APNs leg for FCM→device |
| — | Prerequisites | Partial — drop Xcode/APNs; add Android SDK/device |
| 1 | Install and configure ngrok | Reuse unchanged |
| 2 | Start the backend locally | Reuse unchanged |
| 3 | Obtain and use ngrok HTTPS URL | Reuse — wording: “device” not “iPhone” |
| 4 | Generate and open iOS workspace | Rewrite — Android Studio / Capacitor sync |
| 5 | Firebase + APNs setup | Rewrite — Firebase Android only; no APNs |
| 6 | Notification Debug Panel override | Reuse unchanged |
| 7 | Firebase and Xcode checklist | Rewrite — Android manifest / Gradle checklist |
| 8 | iOS-specific testing notes | Rewrite — Android delivery caveats |
| 9 | Recommended debug workflow | Reuse — replace Xcode console with logcat |
| 10 | Sample curl commands | Reuse — change platform to android |
| 11 | Troubleshooting | Partial — keep ngrok/API rows; replace push rows |
| 12 | Key source files | Reuse unchanged |
| 13 | Related docs | Extend — link Android build/device guides |
Sections reusable unchanged (or near-unchanged)
These blocks can be carried into doc/local-android-testing-ngrok.md (proposed name) with at most global find-replace (“iPhone” → “Android device”, “Mac” tunnel audience unchanged).
notification-wakeup-service startup (iOS §1 Terminal A, §2)
- Clone notification-wakeup-service,
npm install,.envfrom.env.example export PORT=3000(or port from that repo’s README)npm run dev- Local verify:
curl -sS http://localhost:3000/health - Firebase Admin service account for the backend (
GOOGLE_APPLICATION_CREDENTIALS) — same project can serve iOS and Android apps
ngrok setup (iOS §1)
brew install ngrok/ngrok/ngrok(or download)ngrok http 3000in a second terminal- Use HTTPS forwarding URL; free tier URL rotation note
- ngrok inspect UI at
http://127.0.0.1:4040
ngrok account creation (iOS §1 “Account and auth token”)
- Sign up at dashboard.ngrok.com
ngrok config add-authtoken YOUR_AUTHTOKEN_HERE
Obtaining HTTPS URL (iOS §3)
- Copy
https://….ngrok-free.appfrom Forwarding line - No trailing slash in debug panel
- Mac-side tunnel test:
export NGROK_URL=…andcurl "$NGROK_URL/health"
Backend override configuration (iOS §6)
- Non-production build required for Notification Debug Panel
- Account → Show All General Advanced Functions →
/dev/notifications - Notification Backend URL, Save Backend URL
localStorage:notificationDebug.backendBaseUrl,notificationDebug.testMode- Optional programmatic override via
@/services/notifications(setBackendBaseUrl,setTestMode,getNotificationApiBaseUrl)
Debug panel usage (iOS §6 table, §8 “Two Simulate WAKEUP_PING buttons”)
| Control | Android relevance |
|---|---|
| Notification Backend URL | Same |
| Test Mode | Same (testMode: true on API) |
| Register Token Now | Same (POST /notifications/register) |
| Refresh Notifications | Same |
| Simulate WAKEUP_PING (backend) | Same — isolates ngrok + refresh without FCM |
| Wakeup Ping Simulator | Same — exercises handleCapacitorPushNotificationReceived path |
Event Log [Notifications] |
Same |
| Pending Notification Inspector | Same concept; confirm Android plugin inspector behavior in daily-notification-plugin |
testMode usage (iOS §6, §10)
- Default-on when unset in storage (
NotificationDebugConfig.ts) - Sent on register and refresh payloads
- Backend/debug endpoints accept
testMode: truefor dev traffic
Refresh endpoint testing (iOS §9 steps 5, §11 “Refresh endpoint unreachable”)
- Panel Refresh Notifications → expect Event Log + ngrok
POST /notifications/refresh - Simulate WAKEUP_PING (backend button) for API-only path
- Troubleshooting table for network error, 404, wrong port, stale URL
curl examples (iOS §10)
Reuse structure; only payload deltas for Android doc:
export BASE="https://abc123.ngrok-free.app"
$BASE/health— unchanged$BASE/notifications/register— set"platform": "android"$BASE/notifications/refresh— set"platform": "android"$BASE/debug/send-wakeup— unchanged shape; confirm deviceId/token contract in notification-wakeup-service README
App still uses Capacitor.getPlatform() for platform in NotificationService.ts (ios | android).
Shared architecture concepts (intro + silent wake sequence)
Reusable narrative (edit diagram only):
- FCM data message with
data.type = "WAKEUP_PING" - Capacitor
pushNotificationReceived→handleCapacitorPushNotificationReceived() POST {backend}/notifications/refreshwithtestModenextNotifications→applyNotificationRefreshPayload()→ daily-notification-plugin clear + schedule
Repos table (notification-wakeup-service, crowd-funder-for-time-pwa, daily-notification-plugin) — unchanged.
Key source files (iOS §12)
Same files apply on Android Capacitor builds:
NotificationDebugConfig.ts,NotificationDebugEvents.ts,notificationLog.tsNotificationService.ts,NativeNotificationService.tsfirebaseMessagingClient.ts,NotificationDebugPanel.vue,main.capacitor.ts
Recommended debug workflow (iOS §9) — reuse with tooling swap
Steps 1–5, 8–9 unchanged. Replace step 7:
- iOS: Xcode console →
[Notifications] pushNotificationReceived type=WAKEUP_PING - Android:
adb logcatfiltered on app tag /[Notifications](document exact filter in Android guide)
iOS-specific sections — must rewrite for Android
Architecture diagram (intro)
iOS today: Mac → ngrok → app; FCM → APNs → iPhone.
Android doc: FCM → device directly (no APNs). Update ASCII diagram and caption (“silent push” on Android is still FCM data; delivery rules differ).
Prerequisites (intro list)
| iOS prerequisite | Android replacement |
|---|---|
| Mac with Xcode | Android Studio, JDK 17+, ANDROID_HOME, adb — see android-physical-device-guide.md |
| Physical iPhone | Physical Android device (emulator possible for some steps but not representative for Doze/OEM/battery) |
| Firebase with APNs for bundle ID | Firebase with Android app (app.timesafari package name) |
| Non-production build | Same — e.g. build:android:dev / build:android:test |
Remove: “simulator is not sufficient for reliable silent push / APNs”.
Add: emulator vs physical device guidance for FCM and background limits.
§4 — Generate and open the iOS workspace
Replace entirely with Android equivalent:
npm installnpm run build:android:devorbuild:android:test(non-production for debug panel)npx cap sync androidif needed- Open
android/in Android Studio - Run on physical device (USB debugging)
VITE_FIREBASE_*in Capacitor web buildinitializeNativePushAndFirebaseMessaging()inmain.capacitor.ts— same entry point
Do not reference .xcworkspace, signing in Xcode, or build:ios:* except as cross-link to iOS doc.
§5 — Firebase + APNs setup (first-time setup)
Keep (Android-relevant portions only):
- Firebase account / Spark plan sufficient for FCM
- Create Firebase project
- Register Android app in Firebase (package name
app.timesafarifromcapacitor.config.ts) - Download
google-services.json→android/app/(project may gitignore this file — document secure handling) - Firebase Admin service account for notification-wakeup-service — same as iOS §5 tail
Remove entirely:
- Register iOS app in Firebase (or move to “shared project” sidebar: one Firebase project, two apps)
- GoogleService-Info.plist / Xcode drag-and-drop
- Create APNs Authentication Key (.p8)
- Upload APNs key to Firebase
- Enable iOS capabilities (Push Notifications, Background Modes → Remote notifications)
Add in Android guide (see next major section):
- Gradle plugin /
google-servicesclasspath if not already in repo POST_NOTIFICATIONSpermission (API 33+)- Default notification channel / Capacitor Push Notifications Android setup
- SHA-1/SHA-256 only if using Firebase features that require it (note whether wakeup testing needs Play App Signing keys)
§5 verify checklist — iOS-only bullets
Replace:
- “Xcode without Firebase/plist errors” → Android Studio build;
google-services.jsonpresent - “iOS push permission prompt” → Android 13+ notification permission + older grant model
- “content-available style payload” → Android high-priority data message / FCM options as implemented by notification-wakeup-service (document actual payload; no APNs
content-available)
§7 — Firebase and Xcode checklist (iOS)
Replace with Android checklist, e.g.:
| Item | Action |
|---|---|
| Application ID | app.timesafari in capacitor.config.ts, android/app/build.gradle, Firebase Android app |
| google-services.json | In android/app/; not committed if gitignored — local copy per developer |
| Gradle | Google services plugin applied (verify repo’s current build.gradle) |
| Permissions | POST_NOTIFICATIONS (API 33+); manifest entries for FCM |
| FCM token | Debug panel Register Token Now + ngrok POST /notifications/register |
| No APNs | N/A on Android |
§8 — iOS-specific testing notes
Replace with Android-specific sections (draft topics below). Do not port:
- APNs silent delivery / Simulator unreliability (iOS framing)
- Force-quit via app switcher (iOS-specific policy)
- Low Power Mode (iOS) — Android has different battery saver APIs
- Focus / Do Not Disturb (iOS naming)
Port with Android wording:
- Two Simulate WAKEUP_PING buttons table — unchanged behavior
§11 — Troubleshooting (partial)
Reuse as-is:
- Refresh endpoint unreachable (ngrok, URL, 404, CORS note)
- Stale ngrok URL
- Plugin / JWT errors after refresh
Rewrite:
| iOS troubleshooting | Android replacement |
|---|---|
Push permission + VITE_FIREBASE_* + Xcode log |
Permission (runtime POST_NOTIFICATIONS), logcat, Firebase Android config |
| Silent push not waking — backgrounded not force-quit, APNs key, wait 30–120s | FCM high-priority data, force-stop (STOP from settings), Doze, battery optimization, OEM autostart, token mismatch |
| Physical device + provisioning profile | USB debugging, correct build variant, Play vs debug signing if relevant |
§13 — Related docs
Keep iOS-centric links as “see also”; add:
- android-physical-device-guide.md
BUILDING.md— Android build commands (build:android:*)- daily-notification-plugin Android docs (exact alarm, pending inspector on Android)
Android-Specific Topics Required
These sections do not exist in the iOS guide (or exist only by analogy) and must be written for the Android notification testing doc.
Firebase project setup
- Use the same Firebase project as iOS when testing the same backend, or document a dedicated
timesafari-devproject. - Add an Android app with package name
app.timesafari. - Enable Cloud Messaging (default on new projects).
- Download
google-services.jsonand install underandroid/app/. - Note:
android/.gitignoremay excludegoogle-services.json— developers copy locally; never commit secrets.
google-services.json
- Placement:
android/app/google-services.json - Sync after add:
npx cap sync android, rebuild in Android Studio - Verify build merges Firebase config (no “missing google-services” Gradle errors)
- Relationship to
VITE_FIREBASE_*for the web layer / Capacitor JS Firebase initialization
Android notification permissions
- Android 13+ (API 33):
POST_NOTIFICATIONSruntime permission — required for notification display; document interaction with data-only FCM wake (may still deliver to app code when permission denied — verify against current app behavior and document accurately). - Android 12 and below: install-time grant model; fewer runtime prompts.
- App Settings → Notifications — manual enable path for testers.
- Link notification-permissions-and-rollovers.md for product-level permission UX.
FCM token handling
- Token obtained via Capacitor Push Notifications +
firebaseMessagingClient.ts(same JS path as iOS). - Register Token Now in debug panel →
POST /notifications/registerwithplatform: "android". - Token rotation: when to re-register; duplicate skip behavior in panel.
- Ensure notification-wakeup-service stores/sends to the token shown in the panel for
/debug/send-wakeup. - Optional:
adbcannot easily read FCM token — panel is source of truth (same as iOS).
Android background delivery behavior
- FCM data messages handled in foreground/background per Capacitor plugin and
NativeNotificationService.ts. - No APNs intermediary — document expected latency vs iOS.
- High-priority FCM for wakeup testing (align with backend message options).
- App in background vs foreground vs killed — different from iOS “swipe away” story:
- Force stop (Settings → Force stop): delivery often blocked until user launches app again (stricter than iOS “backgrounded”).
- Recent apps swipe: behavior varies by OEM/Android version — document “test with Home button background, not force stop.”
pushNotificationReceived/ listener registration at startup (main.capacitor.ts).
Doze Mode
- Device idle → deferred network and job execution.
- Testing: use
adb shell dumpsys deviceidle(document safe dev-only commands) or unplugged idle wait. - Explain why
/debug/send-wakeupmay succeed on server but device wakes late. - Whitelisting app for tests (developer settings) — use cautiously; note production users won’t do this.
Battery optimization
- Settings → Apps → TimeSafari → Battery → Unrestricted vs Optimized.
- Manufacturer “battery saver” modes that restrict background network.
- Recommend Unrestricted (or equivalent) for local wakeup validation; warn that production users may remain optimized.
OEM restrictions (Samsung, Xiaomi, Oppo, etc.)
- Autostart / Background activity / Battery menus on Samsung, Xiaomi (MIUI), Oppo/ColorOS, Huawei, OnePlus, etc.
- Symptom: FCM works on Pixel but not on OEM device until autostart enabled.
- Provide a short “if wake fails on OEM, check…” checklist without exhaustive per-OEM screenshots (link community docs if needed).
- Physical device testing should include at least one stock-ish device (Pixel) and one OEM device when possible.
Proposed outline for doc/local-android-testing-ngrok.md
Suggested section order mirroring iOS doc for easy maintenance:
- Title, audience, goal (Android physical device + ngrok + wakeup service)
- Architecture overview (FCM direct to Android)
- Prerequisites (Android Studio, device, Firebase Android app, non-prod build)
- ngrok install, account, tunnel (reuse iOS §1)
- Start notification-wakeup-service (reuse iOS §2)
- ngrok HTTPS URL (reuse iOS §3)
- Build and open Android project (new, replaces iOS §4)
- Firebase setup for Android (new, replaces iOS §5 — no APNs)
- Notification Debug Panel (reuse iOS §6)
- Android configuration checklist (new, replaces iOS §7)
- Android-specific testing notes (new, replaces iOS §8)
- Recommended debug workflow (reuse iOS §9 + logcat)
- Sample curl commands (reuse iOS §10 +
platform: "android") - Troubleshooting (merge reusable + Android push rows)
- Key source files (reuse iOS §12)
- Related docs (iOS doc + Android device guide + BUILDING)
Wording and terminology substitutions
When adapting reused sections:
| iOS doc term | Android doc term |
|---|---|
| iPhone | Android phone / device |
| Xcode console | logcat / Android Studio Logcat |
build:ios:dev / test |
build:android:dev / test |
GoogleService-Info.plist |
google-services.json |
| APNs / silent push | FCM data message / high-priority data |
| Bundle ID | Application ID / package name (app.timesafari) |
| Physical iPhone required for APNs | Physical device strongly recommended for Doze/OEM/FCM realism |
platform: "ios" in curl |
platform: "android" |
Gaps to resolve while writing the Android guide
Research during authoring (code + notification-wakeup-service + daily-notification-plugin):
- Exact FCM Android message priority and payload fields for
WAKEUP_PING(parity with iOS data message). - Whether
POST_NOTIFICATIONSdenial blocks data message delivery to JS listeners on API 33+. - Gradle/Firebase plugin versions already in
android/— document exact files to touch. - Android Pending Notification Inspector parity with iOS panel section.
- Whether emulator with Google Play image is acceptable for minimal FCM smoke tests vs mandatory physical device for wakeup SLA testing.
Document maintenance
| Document | Role |
|---|---|
| local-ios-testing-ngrok.md | Canonical iOS + ngrok workflow (unchanged by this analysis) |
| This file | Reuse vs rewrite matrix and Android topic backlog |
Future local-android-testing-ngrok.md |
Operator guide for Android testers |
When backend or debug panel behavior changes, update both platform guides’ shared sections in lockstep (or extract shared “ngrok + debug panel” snippet later — out of scope unless requested).