Re-enable AdMob with bottom banners and GDPR consent - #1
Merged
Conversation
The publisher account (pub-8638037215789792) was reinstated on 2026-08-24 after a suspension for invalid traffic, so ad loading is uncommented in MyApp, MainActivity and QrGeneratorActivity. Banners now sit at the BOTTOM of both screens instead of the top. The previous layout placed the banner directly above the camera viewfinder, where hands moving to aim at a QR code brushed the ad and produced clicks with near-zero dwell time — the most likely cause of the strike. On MainActivity the flash controls keep a 24dp gap above the banner so a mistimed tap cannot reach it. Release builds now fail outright when any AdMob secret is missing, rather than silently substituting Google's test IDs. The check runs in a taskGraph.whenReady hook so debug-only builds still configure without them. The interstitial loader in Utils.kt is restored but deliberately left unwired: interstitials carry the highest invalid-traffic risk, so it should only be attached behind a frequency cap. Verified on a physical device — both screens render the 320x50 test banner at the bottom with no crash. Still missing: a GDPR/UMP consent flow, which is required before serving ads to EEA/UK users.
EU/UK law requires consent before personal data is used for ad personalisation, and Google passes that obligation to publishers through its EU User Consent Policy. Without it, a single European user seeing an ad is a policy breach — on an account that was only just reinstated. ConsentManager resolves consent through the User Messaging Platform and only then initialises the Ads SDK. MobileAds.initialize moves out of MyApp for two reasons: the SDK must not start before consent resolves, and showing the form needs an Activity context. Both ad-bearing activities now build their AdView inside the consent callback rather than in onCreate. The callback can fire twice — once from cached consent, once after the background refresh — so init is guarded by an AtomicBoolean and showBanner() bails when the container already holds a view. On network or SDK failure the flow falls back to the cached consent state instead of leaving the app permanently ad-free, matching Google's reference flow. No new dependency: user-messaging-platform 3.2.0 already arrives transitively via play-services-ads 24.5.0. An optional admob_test_device_id in local.properties forces EEA geography on debug builds so the form can be exercised locally; release ignores it. Verified on a physical device: outside the EEA no dialog appears and the banner loads; with EEA forced the form appears before any ad, and refusing stores IABTCF_PurposeConsents=00000000000. Ads still serve after a refusal, which is correct — under TCF that answer refuses personalisation, not ads. Also adds ADMOB_ROADMAP.md capturing why banners sit at the bottom, why WA Direct was rejected as a banner host, and the frequency-cap design for the interstitial that is still deliberately unwired.
The three ad keys used three different naming shapes — ADMOB_APPLICATION_ID, BANNER_AD_ID, INTERSTITIAL_AD_ID — so they never sorted or grouped together in local.properties, the CI env block, or the GitHub secrets list. They now share one prefix: ADMOB_APPLICATION_ID -> ADMOB_APP_ID BANNER_AD_ID -> ADMOB_BANNER_ID INTERSTITIAL_AD_ID -> ADMOB_INTERSTITIAL_ID This covers the local.properties keys, the env var fallbacks, the CI workflow, and the cppFlags macro names consumed by native-lib.cpp. The pre-existing admob_test_device_id already matched. Renaming the C++ macros is the risky half: a mismatch between cppFlags and TOSTRING() compiles cleanly but bakes the macro *name* into the library instead of the id. Verified by grepping the built .so — both test ids are present and neither macro name survives — and confirmed at runtime on device with the banner rendering. Also corrects the gatherConsent KDoc, which claimed onReady is never called when consent is refused. Device testing showed otherwise: under TCF a refusal rejects personalisation rather than ads, so canRequestAds() stays true and the callback still fires. The old wording would lead someone to "fix" correct behaviour. GitHub secrets cannot be renamed in place, so the three must be re-created under their new names before the next release build; ADMOB_ROADMAP.md records this.
|
Thanks for opening this pull request! Please check out our contributing guidelines. |
|
Congrats on merging your first pull request! We here at behaviorbot are proud of you! |
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.
Publisher
pub-8638037215789792was reinstated on 2026-08-24 after a suspension for invalid traffic, so ad loading is switched back on — with the layout and safeguards changed to address what likely caused the strike.Banners moved to the bottom
The pre-suspension layout placed the banner directly above the camera viewfinder. Hands moving to aim the phone at a QR code brushed the ad, producing clicks with near-zero dwell time, which is what invalid traffic looks like in Google's data. Both ad-bearing screens now put the banner at the bottom, and MainActivity's flash controls keep a 24dp gap above it.
WA Direct was considered as a banner host and rejected — it has a country-code picker, a number field and a send button, i.e. more tap targets in a tighter flow than QR Generator's type-then-tap-once.
GDPR consent (UMP)
EU/UK law requires consent before personal data is used for ad personalisation, and Google passes that obligation to publishers. Without it a single European user seeing an ad is a policy breach — on an account that was only just reinstated.
ConsentManagerresolves consent through the User Messaging Platform and only then initialises the Ads SDK.MobileAds.initializemoved out ofMyAppfor two reasons: the SDK must not start before consent resolves, and showing the form needs an Activity context. Both activities build theirAdViewinside the consent callback.No new dependency —
user-messaging-platform:3.2.0already arrives transitively viaplay-services-ads:24.5.0.Release builds fail without secrets
Previously a missing secret only logged a warning and silently substituted Google's test IDs, so a production build could ship serving no real ads. It now fails outright. The check runs in a
taskGraph.whenReadyhook so debug-only builds still configure without them.Naming
The three keys used three different shapes and never grouped together. They now share one prefix:
ADMOB_APPLICATION_IDADMOB_APP_IDBANNER_AD_IDADMOB_BANNER_IDINTERSTITIAL_AD_IDADMOB_INTERSTITIAL_IDThis covers
local.properties, the env fallbacks, the CI workflow, and thecppFlagsmacros consumed bynative-lib.cpp.Verified on a physical device
IABTCF_gdprApplies=0, no dialog, banner loadsIABTCF_PurposeConsents=00000000000stored; banner still loadsThe third row is correct rather than a bug: under TCF "Do not consent" refuses personalisation, not ads, so
canRequestAds()stays true and non-personalised ads still serve. ThegatherConsentKDoc claimed the opposite and has been corrected.The C++ macro rename was verified by grepping the built
.so— both test ids present, neither macro name surviving — since a mismatch there compiles cleanly but bakes the macro name into the library.Before merging
The three GitHub secrets must be re-created under their new names (Settings → Secrets and variables → Actions). Secrets cannot be renamed in place, and the workflow now reads the new names, so the release build will fail until this is done.
Not included
ADMOB_ROADMAP.mdrecords the frequency-cap design for when it is added, after banners have run clean for 2-4 weeks.