Firebase ships in the Play Store build and nowhere else. The :app module has a distribution
flavor dimension with two flavors:
| Flavor | Firebase | Ships as |
|---|---|---|
opensource |
none — no Google Play Services libraries at all | GitHub release APK, and the default for local builds |
play |
Analytics + Crashlytics | Play Store AAB |
Everything else about the two builds is identical: same applicationId, same version, same
install backends (Default / Shizuku / Root / Dhizuku).
./gradlew :app:assembleOpensourceDebug # day-to-day debug build
./gradlew :app:assembleOpensourceRelease # GitHub release APK
./gradlew :app:bundlePlayRelease # Play Store AABThe play variants only exist when app/src/play/google-services.json is present. Without it
assembleDebug / assembleRelease build the opensource flavor alone, exactly as they did
before the dimension was added, and any *Play* task fails with "task not found" rather than
quietly producing a Play build with Firebase missing. With the file present those two build
both flavors, so name the variant explicitly when you only want one.
The file is not in the repository — it carries our Firebase project's app id and API key, and this repo is public.
- In the Firebase console, open the project and add (or
select) an Android app with package name
app.pwhs.universalinstaller. - Download
google-services.json. - Put it at
app/src/play/google-services.json. It is gitignored; do not commit it.
For CI, the same file is stored base64-encoded in the GOOGLE_SERVICES_JSON repository secret and
written back by the Decode Firebase config step of .github/workflows/publish-release.yml:
base64 -i app/src/play/google-services.json | pbcopy # macOSContributors don't need any of this. A clone without the file builds and runs the opensource
flavor normally.
play release builds are minified, so Crashlytics needs the R8 mapping file to show real class
names. The Crashlytics Gradle plugin uploads it as part of bundlePlayRelease — nothing to do by
hand — and proguard-rules.pro keeps SourceFile,LineNumberTable so reports carry line numbers.
The opensource flavor turns that upload off: it has no Firebase app id to upload against.
The seam is app.pwhs.universalinstaller.telemetry.Telemetry. Code under src/main only ever
calls that; each flavor's src/<flavor>/…/TelemetrySinkFactory.kt decides what it does — Firebase
on play, nothing at all on opensource.
Events and parameters are declared in TelemetryEvents, currently:
| Event | Parameters | Answers |
|---|---|---|
install_started |
method, apk_count |
— |
install_result |
method, result (success / failure / cancelled), failure |
Which backends actually work, and how they fail |
backend_health |
method, healthy |
How many people set up Shizuku or root and arrive with it broken |
default_installer_set |
method, enabled, result (+ blocked) |
Whether taking over the installer role succeeds, per backend |
feature_used |
feature |
Which secondary features are worth maintaining |
method is the backend that actually ran — default, shizuku, root, dhizuku,
manual_shizuku, manual_root — and is also set as the install_method user property so crashes
can be split by backend. Firebase collects screen views and sessions on its own; we add nothing
there.
backend_health is the one that can't be derived from the others: install_result only describes
installs that happened, so someone who configured Shizuku, had it break and gave up never appears
in it. It fires from BackendSelfHeal once per cold start, and only for backends the user turned
on — so the denominator is people who chose that backend.
feature_used is one event name with a feature parameter rather than one name per feature:
lan_share, virustotal_scan, apk_backup, installer_profile, batch_install, obb_copy,
url_download, uninstall. It fires when a feature is used, not when its screen is opened.
Warnings and errors logged through Timber become Crashlytics breadcrumbs, and Timber.e(throwable)
is additionally reported as a non-fatal.
Rule for anything added here: never report what the user installed. No package names, no app names, no file names, no URIs. What we want to learn is which install backends work and where they fail, and none of that is needed for it.
Firebase Analytics merges two permissions into the play manifest that opensource doesn't have:
com.google.android.gms.permission.AD_ID
android.permission.ACCESS_ADSERVICES_AD_ID
They must be declared in the Play Console's Data Safety form. The app has no ads and doesn't use
attribution, so they can also just be dropped — put this in app/src/play/AndroidManifest.xml
(with xmlns:tools declared on the root element):
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />Analytics keeps working without them; it just can't tie events to an advertising ID. Left in place for now because that's Firebase's default and removing it is a product call.
assemblePlayDebug collects and sends like the release build does — deliberately, so the
integration can be verified before shipping. If dev noise becomes a problem in the console, add
app/src/playDebug/AndroidManifest.xml with:
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />Reporting is on by default and the user can turn it off in two places, both writing
SharedPrefsKeys.ANALYTICS_ENABLED (absent means on):
- An onboarding page, shown only when
Telemetry.isCollecting— that is, on theplaybuild. It presents the switch already on. - Settings → Privacy, a section that likewise does not exist in the
opensourcebuild, because a switch there would promise control over something that never happens.
Both call Telemetry.setCollectionEnabled, which reaches
FirebaseAnalytics.setAnalyticsCollectionEnabled and
FirebaseCrashlytics.setCrashlyticsCollectionEnabled immediately, so turning it off stops the next
event rather than the next launch. App.applyTelemetryPreference re-applies the preference at
every start: Firebase persists the flag itself, but the preference is what should decide — including
after a restore onto a new device, where the preference travelled and Firebase's own state did not.
It runs before BackendSelfHeal, which is the first thing that would otherwise report.
The consent strings are English-only. The app ships 15+ locales, so
onboarding_analytics_* in :core and setting_analytics_* in :app still need translating.
The Play listing's Data Safety section needs to declare crash logs, diagnostics, and device identifiers.