# Android Local Notification Testing — Planning Analysis **Created:** 2026-06-02 **Source document:** [local-ios-testing-ngrok.md](./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](./android-physical-device-guide.md) — USB, `adb`, build/run commands - [notification-system-overview.md](./notification-system-overview.md) - [notification-from-api-call.md](./notification-from-api-call.md) - [notification-permissions-and-rollovers.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`, `.env` from `.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 3000` in 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.app` from Forwarding line - No trailing slash in debug panel - Mac-side tunnel test: `export NGROK_URL=…` and `curl "$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`, `notificationDebug.bypassAuth` - Optional programmatic override via `@/services/notifications` (`setBackendBaseUrl`, `setTestMode`, `setBypassAuth`, `getNotificationApiBaseUrl`) ### Debug panel usage (iOS §6 table, §8 “Two Simulate WAKEUP_PING buttons”) | Control | Android relevance | |---------|-------------------| | Notification Backend URL | Same | | Test Mode | Same (`testMode` in JSON body) | | Skip JWT Authentication | Same — explicit opt-in for unauthenticated local backends (default off) | | 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: true` for 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: ```bash 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): 1. FCM **data** message with `data.type = "WAKEUP_PING"` 2. Capacitor `pushNotificationReceived` → `handleCapacitorPushNotificationReceived()` 3. `POST {backend}/notifications/refresh` with `testMode` 4. `nextNotifications` → `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.ts` - `NotificationService.ts`, `NativeNotificationService.ts` - `firebaseMessagingClient.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 logcat` filtered 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](./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 install` - `npm run build:android:dev` or `build:android:test` (non-production for debug panel) - `npx cap sync android` if needed - Open `android/` in Android Studio - Run on physical device (USB debugging) - `VITE_FIREBASE_*` in Capacitor web build - `initializeNativePushAndFirebaseMessaging()` in `main.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.timesafari` from `capacitor.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-services` classpath if not already in repo - `POST_NOTIFICATIONS` permission (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.json` present - “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](./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-dev` project. - Add an **Android** app with package name **`app.timesafari`**. - Enable **Cloud Messaging** (default on new projects). - Download **`google-services.json`** and install under `android/app/`. - Note: `android/.gitignore` may exclude `google-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_NOTIFICATIONS` runtime 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](./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/register` with `platform: "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: `adb` cannot 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-wakeup` may 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: 1. Title, audience, goal (Android physical device + ngrok + wakeup service) 2. Architecture overview (FCM direct to Android) 3. Prerequisites (Android Studio, device, Firebase Android app, non-prod build) 4. ngrok install, account, tunnel (**reuse iOS §1**) 5. Start notification-wakeup-service (**reuse iOS §2**) 6. ngrok HTTPS URL (**reuse iOS §3**) 7. Build and open Android project (**new**, replaces iOS §4) 8. Firebase setup for Android (**new**, replaces iOS §5 — no APNs) 9. Notification Debug Panel (**reuse iOS §6**) 10. Android configuration checklist (**new**, replaces iOS §7) 11. Android-specific testing notes (**new**, replaces iOS §8) 12. Recommended debug workflow (**reuse iOS §9** + logcat) 13. Sample curl commands (**reuse iOS §10** + `platform: "android"`) 14. Troubleshooting (**merge reusable + Android push rows**) 15. Key source files (**reuse iOS §12**) 16. 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**): 1. Exact FCM Android message priority and payload fields for `WAKEUP_PING` (parity with iOS data message). 2. Whether `POST_NOTIFICATIONS` denial blocks data message delivery to JS listeners on API 33+. 3. Gradle/Firebase plugin versions already in `android/` — document exact files to touch. 4. Android **Pending Notification Inspector** parity with iOS panel section. 5. 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](./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).