Repository navigation
Description
What feature would you like to see?
A documented, supported pre-initialization migration control for Basic Consent on an Android app upgrade when a previous release persisted setAnalyticsCollectionEnabled(true) and consent grants, but the replacement release requires a new explicit Analytics opt-in.
The Basic Consent guide documents both the Manifest default and the persisted setter taking precedence over that default. We are not claiming that precedence is itself a bug. The missing contract is how to safely migrate an existing installation before automatic-event admission, including when users skip intermediate app releases.
Ideally this would be a documented startup option that temporarily takes precedence over legacy persisted enablement until the application explicitly grants consent in the current process. Alternatively, please point us to an existing supported public-API sequence that provides that boundary. This is a request for SDK/API support, not a backend DebugView troubleshooting request. I understand Analytics implementation is not open source in this repository; please redirect if this SDK feature request belongs in another official tracker.
How would you use it?
Keep optional Analytics available after explicit opt-in in the same app version, while collecting no Analytics events before that new choice. FCM and consent-eligible advertising must remain independent; advertising eligibility is not Analytics consent. Permanent Analytics deactivation and switching silently to Advanced Consent would not meet this requirement.
Environment of the synthetic reproduction
- Android Studio: not used; command-line Gradle build.
- JDK 17, Gradle 8.14.3, Android Gradle Plugin 8.12.2.
compileSdk/targetSdk36,minSdk24.- Disposable ARM64 Google APIs/Play Store emulator, API 37.1.
- Explicit Maven dependency
com.google.firebase:firebase-analytics:23.2.0; dependency inspection resolved measurement dependencies to 23.2.0. - Also present:
firebase-messaging:25.0.2,play-services-ads:25.0.0. No FCM token or ad request was made in the consent-first sequence described below. - That run selected the installed Google Play services remote measurement module, whose diagnostic version was 194. This is an internal module label, not a Maven SDK version. Exact bundled-module and older-Android equivalence have not been established.
Reproduction steps
- Use a disposable test application with a valid test
google_app_idresource and normal Firebase initialization. Never use production user data. Keep the same application ID and signing certificate throughout. - Install seed versionCode 1. Using only public APIs, grant all four consent purposes and call
setAnalyticsCollectionEnabled(true). Launch and verify an explicit event and automatic events as positive controls. Force-stop afterward. - Install a higher-version replacement APK using
adb install -rwithout uninstalling or clearing data. Its Manifest hasfirebase_analytics_collection_enabled=false. A non-exported applicationContentProviderwithandroid:initOrder="1000"callssetConsent(all four DENIED)followed bysetAnalyticsCollectionEnabled(false)beforeApplication.onCreate. Inspect the merged Manifest: the ordinaryFirebaseInitProviderin this fixture had initOrder 100. - Cold-start the replacement without granting the new first-party opt-in. Compare frontend markers and package-attributed measurement-service diagnostics. Enable verbose
FA/FA-SVClogs on the disposable emulator; do not rely on DebugView absence as proof of no event collection. - As controls, compare with a legacy-enabled upgrade without the early-denial provider, and separately exercise same-process explicit grant, withdrawal, and denied restart. Restore the seed grants with public APIs before comparing another higher-version upgrade; do not edit SDK preference files.
These are excerpts of the exercised fixture, not an independently re-executed standalone sample project. No public reproduction repository or project configuration is attached.
Manifest excerpt from the consent-first fixture (inside <application>):
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<meta-data android:name="com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT" android:value="true" />
<provider
android:name=".ConsentProvider"
android:authorities="${applicationId}.consentprobe"
android:exported="false"
android:initOrder="1000" />INTERNET permission was retained. Ads SDK configuration used Google's sample app ID. The fixture did not include the four Manifest consent-default keys; denial was applied through the public API on upgrade. Adding those defaults and migrating to other SDK/runtime versions are separate unverified cases, not results claimed here.
Core public-API helper, called with true by the seed and with false by the replacement provider:
static void applyConsent(Context context, boolean granted) {
FirebaseAnalytics analytics = FirebaseAnalytics.getInstance(context);
EnumMap<FirebaseAnalytics.ConsentType, FirebaseAnalytics.ConsentStatus> purposes =
new EnumMap<>(FirebaseAnalytics.ConsentType.class);
for (FirebaseAnalytics.ConsentType purpose : FirebaseAnalytics.ConsentType.values()) {
purposes.put(purpose, granted
? FirebaseAnalytics.ConsentStatus.GRANTED
: FirebaseAnalytics.ConsentStatus.DENIED);
}
Log.i("ConsentProbe", "before setConsent: " + granted);
analytics.setConsent(purposes);
analytics.setAnalyticsCollectionEnabled(granted);
Log.i("ConsentProbe", "both public calls returned: " + granted);
}Replacement provider excerpt (the other required ContentProvider overrides are no-ops):
@Override public boolean onCreate() {
applyConsent(getContext(), false);
return true;
}All-purpose seed grants/denials are a synthetic diagnostic contrast, not a recommendation to couple advertising and Analytics choices in an application.
Observed ordering and limits
In the consent-first no-clear upgrade capture, taking the application's “both public calls returned: false” marker as T+0:
| Relative time | Observation |
|---|---|
| T+817 ms | Frontend storage-denial processing log |
| T+939 ms | Frontend collection-disabled processing log |
| T+956 ms | Measurement service logs automatic app_update |
| T+1071 ms | Measurement service logs package storage denial |
| T+1089 ms | Measurement service logs DMA denial |
Explicit application event markers afterward were rejected. The legacy-enabled positive control logged app_update. A later explicit grant admitted application markers; withdrawal rejected subsequent explicit markers, although already-accrued engagement was logged after withdrawal. A bounded denied-restart capture rejected explicit markers; that absence is not a universal no-collection proof.
These observations establish diagnostic event logging/admission order, not transmission of the pre-opt-in event, backend receipt, production-user impact, or an identifier leak. Network transport was not the acceptance measure. No raw user-device logs, project identifiers, credentials, or production configuration are attached.
Issue #5697 discusses events under storage-denied consent and DebugView visibility. This request differs: it concerns Basic collection disabled on a no-clear upgrade from persisted enabled/granted state, rather than expecting Advanced Consent to suppress all events.
Requested clarification / acceptance contract
- Is there a supported public startup/migration control that applies before measurement initialization reads a legacy persisted collection grant, without private preference writes, reflection, undocumented Bundle keys, or a custom ClassLoader?
- If an early public getter/setter is the supported approach, what guarantees ordering across the frontend and the Play services measurement process, and how should an app verify that boundary?
- If this is not supported today, could you add a migration option or document the limitation and the supported alternative? The solution should allow same-release explicit grant/withdraw/restart and users skipping intermediate releases, without disabling unrelated Firebase products.
Activity
ProfSeaquill commentedon Oct 28, 2026
Additional synthetic native reproduction for the legacy persisted-ON migration case, including a provider-before-Activity experiment. This adds an Android 35 / local-measurement-module environment and paired measurement-service database evidence.
Scope: this concerns a legacy SDK grant with unknown consent provenance. It does not imply that valid affirmative consent must be revoked every launch, nor establish production-user impact or universal impossibility.
Environment and preconditions
measurement_enabled=trueandmeasurement_enabled_from_api=true. These prove SDK state, not affirmative user consent.Candidate
Remove
FirebaseInitProvider. Our non-exported provider hasinitOrder=1000. ItsonCreateinitializes Firebase, immediately disables collection through the public API, requestsgetSessionId()with an explicit background completion executor, then waits only for the remaining portion of a two-second deadline measured before initialization. No Firebase calls occur in the Application/Activity callbacks.Relevant manifest metadata:
Core sequence (abridged from the exercised fixture; not a separately rerun standalone sample):
The actual fixture also records callback status and deadlines separately. We do not assume getter completion acknowledges collection OFF or provides a documented synchronization contract. This was an empirical ordering experiment, not a proposed production workaround. Neither consent denial nor reset was invoked in this candidate.
Result
Relative to public collection-OFF return:
_sevent timestampGetter completion was successful, non-null, on
pool-2-thread-1, about 596 ms before the explicit deadline. Provider duration was about 1409 ms. The failure was therefore not a timeout or a main-thread completion-listener deadlock: the automatic session was generated before the fence completed and before any Activity lifecycle.Paired read-only copies of the measurement-service database show raw rows 5 → 6. The only new row was
_s, payload SHA256:ccaaee6b70172d0f9735688d6824480ea3de59c22a535a393ca5619bb09e383bNo additional app-update/first-open row or queued upload bundle appeared in this case. An ADB-listener interruption delayed final capture; recovery copied the same uninterrupted candidate process, without relaunching the Activity/app or running another case. The ordering logs were already captured before that interruption.
We also separately tried removing FirebaseInitProvider and deferring first Firebase construction until after the first Activity resume, immediately followed by public collection OFF. That arrangement also admitted a new automatic session before worker OFF.
Clarification requested
Is there a supported startup/migration control that supersedes legacy unknown-provenance persisted enablement before automatic event admission, while still allowing collection after a valid decision in the same release?
If the correct approach uses a different public API or initialization arrangement, what is its ordering contract across the client and Play services measurement process? We want to avoid private preference writes, undocumented Bundle keys, getter-as-ack assumptions, provider blocking, and treating a multi-release migration as safe when users can skip intermediate releases.
No production configuration, credentials, real-user device logs or database payloads are attached.
YanlongLai commentedon Oct 30, 2026
@ProfSeaquill Thanks for the additional reproduction and for clearly separating local event admission from transmission or backend receipt.
The provider-before-Activity result is particularly useful: in this capture, the new
_sevent precedes worker-side collection OFF and the first Activity callback, despite the public setter having returned. Getter completion therefore does not resolve the startup boundary being requested here. This remains evidence for the tested configuration, rather than proof that every initialization arrangement behaves identically.For the Firebase Analytics team, could you clarify whether there is a supported public initialization or migration mechanism that prevents automatic event admission when an existing installation has persisted collection ON but the app cannot establish the provenance of that consent? The requirement is to keep Analytics disabled until a valid choice, while allowing opt-in in the same release and keeping unrelated Firebase products usable, including for users who skip intermediate releases.
If such a mechanism exists, please point us to its documented ordering guarantees, including the Play services measurement process. If it does not, documenting that limitation and the supported migration alternative would help. We are not proposing provider blocking or getter completion as a production synchronization contract, and this request does not require revoking valid affirmative consent on every launch.
lehcar09 commentedon Oct 30, 2026
Hi @YanlongLai, thank you for reaching out and providing a detailed report.
Currently, there's no documented startup or migration option that override a previously saved
setAnalyticsCollectionEnabled(true)before Analytics starts. There's also no public-API sequence we can point to that guarantees no automatic events are recorded before a new opt-in. The setter's value persists across app launches and overrides the Manifest default until it's called again (Ref). The only documented control that outranks it is by adding thefirebase_analytics_collection_deactivatedin the manifest file, which is permanent for that app version so it doesn't let users opt in with the same release.The order you observed seems to coincide with
app_updateevent; Analytics check for it at startup by comparing the current app version with the last version on the device. CallingsetContentorsetAnalyticsCollectionEnabled(false)early isn't documented to take effect before the check.I agree that this is a gap for Basic Consent migrations. I'll raise this to our engineers as a feature request covering a migration option and/or documentation of the limitation.
One note on the fixture: withdrawing Analytics or Ad storage consent deletes all user properties, including ad personalization consent. If advertising choices need to stay independent, restore that value with
setConsent