Gate reinstaller variant override with privacy-config feature flag#8708
Open
catalinradoiu wants to merge 3 commits into
Open
Gate reinstaller variant override with privacy-config feature flag#8708catalinradoiu wants to merge 3 commits into
catalinradoiu wants to merge 3 commits into
Conversation
Add ReinstallerVariantProtectionFeature so the reinstaller's "ru" variant no longer overwrites a campaign suffix (e.g. DDGRA) that has already been written to statisticsDataStore.variant by the Play Store referrer chain. The feature exposes a list of protected variants via remote settings; when the current variant matches one of them, the reinstaller write is skipped and the campaign suffix is preserved on the ATB. Default behaviour (feature disabled or empty list) keeps today's "reinstaller wins" semantics intact. Task: https://app.asana.com/1/137249556945/project/1211724162604201/task/1215139098848581
…b-variant-override
The new ReinstallerVariantProtectionFeature is the first @ContributesRemoteFeature in this module, so the missing
`ksp project(':anvil-ksp')` declaration only surfaced now — without it the
custom KSP processor never runs and Dagger / Metro both fail with
MissingBinding for the feature interface.
marcosholgado
approved these changes
May 29, 2026
Contributor
marcosholgado
left a comment
There was a problem hiding this comment.
LGTM, left a minor comment
| featureName = "reinstallerVariantProtection", | ||
| ) | ||
| interface ReinstallerVariantProtectionFeature { | ||
| @Toggle.DefaultValue(DefaultFeatureValue.FALSE) |
Contributor
There was a problem hiding this comment.
This is fine but if you use a sub-feature you get more nice things like rollouts so consider doing a sub-feature.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Task/Issue URL: https://app.asana.com/1/137249556945/project/1211724162604201/task/1215139098848581
Description
Today the reinstaller (
isAppReinstall()) listener unconditionally writesstatisticsDataStore.variant = "ru"before ATB init. When a user is both a reinstaller and arrives via an active DDGRA install-referrer campaign, the Play Store referrer chain has already written the campaign suffix intostatisticsDataStore.variant; the reinstaller listener then overwrites it, and the campaign suffix is lost from every outgoing ATB string (/exti,/atb.js, search rewrites, pixels). No current impact (no active DDGRA campaign in over a year), but the next campaign would silently under-report attribution because DDG has more reinstallers than new installers.This PR introduces a new privacy-config-driven feature flag,
reinstallerVariantProtection, which exposes a list of "protected" variants via remote settings:ReinstallAtbListener.beforeAtbInit()now checks the current variant against the protected list and skips thevariant = REINSTALL_VARIANTwrite when it matches. If the feature is disabled, the settings are missing/malformed, the protected list is empty, or the current variant isnull/ not in the list, the listener falls through to today's behaviour (write"ru"). The default (DefaultFeatureValue.FALSE, no settings) preserves the current "reinstaller wins" semantics — important because reinstallers are intentionally excluded from most experiments and the"ru"variant carries that signal.Changes:
ReinstallerVariantProtectionFeature(@ContributesRemoteFeature,AppScope) with aself()toggle and aReinstallerVariantProtectionSettings(variants)data class for JSON parsing.ReinstallAtbListenernow injects the feature and Moshi, parses the protected variants fromself().getSettings(), and gates the variant write onisEnabled()+ list membership.Steps to test this PR
The end-to-end on-device flow needs three pieces of local-only scaffolding on top of the PR commits:
reinstallerVariantProtectionin your local privacy config.test-de-override.patchortest-mq-override.patch) which add diagnostic logs and stubStatisticsSharedPreferences.variantso a campaign-style variant survives theVariantManagerPluginreset. Each patch fakes a different variant; the two patches verify the two branches of the new code path.appReinstalled=truein the build-config prefs) so theisAppReinstall()guard fires.0. One-time setup of the privacy-config patch
Create
privacy-config/privacy-config-internal/local-config-patches/reinstaller-variant-protection.json(gitignored) with this content:[ { "op": "add", "path": "/features/reinstallerVariantProtection", "value": { "state": "enabled", "settings": { "variants": ["de", "df"] } } } ]Create or update
privacy-config/privacy-config-internal/local.properties(gitignored) with:This is the JSON-Patch override mechanism documented in
privacy-config/privacy-config-internal/README.md— it works oninternalDebug/internalReleasebuilds only.1. Verify the protected variant is preserved (positive case)
Apply the DE override test patch:
test-de-override.patchClean install the app on the device and filter the logs by
ReinstallersCampaignLogTagExpected logs:
Key signal: the listener skips the overwrite,
resolved variant='de', and the wire ATB carries thedesuffix.2. Verify the non-protected path still overwrites (counter-test)
Reset the working tree and apply the MQ override test patch instead:
test-mq-override.patchClean install the app on the device and filter the logs by
ReinstallersCampaignLogTagExpected logs:
Key signal: same patched feature config, but because
mqis not in[de, df]the listener overwrites toru— today's behaviour preserved when the variant isn't protected.UI changes