Deny-by-default means the analytics SDK does not initialise, does not open a disk queue, and does not register lifecycle callbacks until your layer says so. “Initialise but do not send” is not the same thing. Many kits still persist identifiers in that mode.
Shape of the wrapper
Expose three calls to the rest of the app: recordBasis(class, source), track(event), and revoke(class). Product code never imports the vendor SDK. Debug builds may include a verbose inspector; release builds must compile that inspector out or guard it behind a flag that QA verifies is false.
We have failed more than one critique because a DEBUG || BuildConfig.INTERNAL flag shipped in a Play internal track that still reached staff devices with production-like profiles. Treat internal tracks as production for consent purposes.
Crash breadcrumbs
Identifier-free crash buffers can live outside the product-analytics basis if your memo says so. They should not include advertising IDs, emails, or precise location “to help reproduce.” If a stack trace contains a user token, that is a logging defect, not a measurement feature.
OEM and pre-install kits
Some Android builds in Thailand arrive with manufacturer analytics already resident. Your wrapper cannot always kill another process. Inventory it, disclose it, and push the relationship into procurement and OS settings guidance. Pretending the wrapper covers silicon partners is a limitation we state plainly in the flagship FAQ.
Testing the default
Automated tests should boot the app with an empty basis store and assert zero analytics hosts in the network log. A single test that only checks the CMP UI is insufficient. The deny-by-default layer is proven by absence.
Sample wrapper sketches are part of Instrument Bench and Field Notes. They are starting points, not drop-in production code — we do not implement your app for you.
Continue with pipeline gates and PDPA at boot.