Files
crowd-funder-for-time-pwa/doc/local-android-testing-analysis.md
Jose Olarte III e0a3f7094f docs(notifications): add Android local ngrok testing guide
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.
2026-06-02 21:33:23 +08:00

401 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 guides **backend + ngrok + in-app debug panel** path is platform-agnostic. Most of sections **13**, **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 repos 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`
- 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: 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 15, 89 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 repos 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 30120s | 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 wont 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).