# Android — Firebase, Google Play Services, and FOSS Distribution ## Overview The app is designed to work on Android devices with and without Google Play Services (GMS). Firebase/FCM is **on by default** for local, test, and production builds. GMS-less devices still run the app; FCM degrades when Play Services is absent. F-Droid and other builds that must omit Firebase opt out explicitly. ## How the switch works `android/app/build.gradle` applies `com.google.gms.google-services` unless `firebaseEnabled` is `false`. `android/gradle.properties` sets `firebaseEnabled=true`, so Android Studio, `./gradlew`, and `scripts/build-android.sh` all pick it up. `-PfirebaseEnabled` and `-PfirebaseEnabled=true` stay on. The config file does not decide the switch. When Firebase is on, `android/app/google-services.json` must exist, be non-empty, and contain `mobilesdk_app_id`. Otherwise configuration fails with an error that names that path. When Firebase is off, the file is ignored even if it is on disk. `scripts/build-android.sh` forwards an opt-out: `FIREBASE_ENABLED=false` adds `-PfirebaseEnabled=false` to its Gradle commands. Default npm scripts leave Firebase on. A `:nostore` script sets `FIREBASE_ENABLED=false` and reuses that script (`npm run build:android:test:nostore:run`). iOS uses the same default. `ios/App/scripts/bundle-firebase-config.sh` copies `ios/App/App/GoogleService-Info.plist` into the app and the share extension. `scripts/build-ios.sh` runs it with `--check` before a device build, and both Xcode targets run it as a build phase. The build and archive `xcodebuild` commands both receive `FIREBASE_ENABLED`. `FIREBASE_ENABLED=false` becomes `FIREBASE_ENABLED=NO` and skips the plist. A `:nostore` script sets that variable (`npm run build:ios:prod:nostore`). A missing or empty plist fails the build when Firebase is on. `:nostore` disables Firebase only. It does not strip other store-linked dependencies (the ML Kit barcode scanner still pulls in Play Services) and does not guarantee the build will run on every device outside the Play Store. ## Build commands | Target | Command | Firebase | |---|---|---| | Local / test / production APK or AAB | `./gradlew assembleRelease` or `bundleRelease` | on | | Play Store with FCM | `./gradlew bundleRelease` | on | | iOS (Xcode or `scripts/build-ios.sh`) | default | on | | F-Droid / FOSS Android | `./gradlew assembleRelease -PfirebaseEnabled=false` or `npm run build:android:test:nostore` | off | | iOS without Firebase | `FIREBASE_ENABLED=false` or `npm run build:ios:prod:nostore` | off | ## Behavior on non-GMS devices When Firebase is enabled (the default), Firebase SDKs check for GMS availability at startup and degrade gracefully if it is absent: - Firebase initializes but detects no GMS - FCM skips token registration silently (no token, no notifications) - The app continues to work normally This means a single Play Store AAB (`bundleRelease`, Firebase on by default) covers both GMS and non-GMS users. GMS users get push notifications; non-GMS users get a fully functional app without them. **Aurora Store** pulls the exact APK from Play Store servers, so Aurora users get whichever variant was uploaded. The default Play Store AAB (Firebase on) is the one to upload. **F-Droid** is stricter: their build policy rejects any APK with GMS dependencies at the binary level, even with graceful degradation. F-Droid submission would require a separate `assembleRelease -PfirebaseEnabled=false` build and a dedicated F-Droid listing. ## Push registration in builds without Firebase A build with `-PfirebaseEnabled=false` contains the `@capacitor/push-notifications` plugin, but no Firebase config. In that build, `PushNotifications.register()` throws `Default FirebaseApp is not initialized` on the Capacitor plugin thread, which kills the app; a JS `try/catch` cannot intercept it. The JS therefore asks native code first. `NotificationInspector.isFirebaseConfigured()` (`android/app/src/main/java/app/timesafari/notifications/NotificationInspectorPlugin.java`) reports whether the `google_app_id` string resource exists; the google-services Gradle plugin generates that resource only when Firebase is enabled, and it is the same signal Firebase's auto-initialization uses. `firebaseMessagingClient.ts` skips `register()` on Android when it is absent (or when the check fails), logging `No Firebase config in this Android build; skipping push registration`. Listener setup and the notification permission request run either way, since local daily notifications need that permission. `NotificationInspector` must be listed in `scripts/restore-local-plugins.js`: `MainActivity` registers plugins after the Capacitor bridge is built, so a plugin missing from `capacitor.plugins.json` reports "not implemented on android" and the check falls back to skipping push. Any code that calls `PushNotifications.register()` or other `FirebaseMessaging` APIs on Android must go through the same check. ## The `google-services.json` file - Gitignored (`android/.gitignore` line 80) — never commit it - Contains Firebase project credentials (project number, app ID, API key) - Required for the default (Firebase on) build. Place it at `android/app/google-services.json` - Ignored when you pass `-PfirebaseEnabled=false`; leaving it on disk does not re-enable Firebase - Obtain from the Firebase console: Project Settings → Your apps → Android app → Download `google-services.json` ## Known GMS dependency: MLKit barcode scanner `@capacitor-mlkit/barcode-scanning` unconditionally depends on `com.google.android.gms:play-services-code-scanner` (present since the plugin was first added at v6.0.0). This merges `com.google.android.gms.version` and `GoogleApiActivity` into the APK manifest regardless of the `-PfirebaseEnabled` flag. Practical impact: - **GMS devices**: barcode scanning works normally - **Non-GMS devices**: barcode scanning fails at scan time (not at startup); the app launches and runs normally otherwise This is an accepted trade-off. Removing it would require either forking the plugin or introducing a `foss` product flavor that excludes the MLKit plugin entirely — work to undertake if/when F-Droid submission is planned. ## Incident: June 2026 Jose Olarte III's `notify-api` branch placed a production `google-services.json` in `android/` to test Firebase Cloud Messaging. The branch was never merged to `master`, but because the file is gitignored it persisted on disk after switching branches. At the time, the Gradle conditional activated Firebase based on file presence alone (no opt-in flag), so all subsequent local builds embedded Firebase and required Google Play Services. This silently broke APK/Aurora/Zapstore distribution. **Fix applied:** deleted `google-services.json` from disk, changed the Gradle conditional to require `-PfirebaseEnabled`, and documented the rule in `AGENTS.md`. ## Incident: September 2026 After the June fix, local builds without `-PfirebaseEnabled` (including every `scripts/build-android.sh` build and the FOSS APKs from v1.4.4 on) crashed at launch as soon as notification permission was granted, because startup code called `PushNotifications.register()` unconditionally. Found while running `npm run build:android:prod:run` on an emulator. **Fix applied:** the `isFirebaseConfigured()` gate described in "Push registration in builds without Firebase", plus adding `NotificationInspector` to `scripts/restore-local-plugins.js` so the check is reachable on Android.