Skip to content

Re-enable AdMob with bottom banners and GDPR consent - #1

Merged
eby-dev merged 3 commits into
masterfrom
feat/re-enable-admob
Aug 24, 2026
Merged

Re-enable AdMob with bottom banners and GDPR consent#1
eby-dev merged 3 commits into
masterfrom
feat/re-enable-admob

Conversation

@eby-dev

@eby-dev eby-dev commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Publisher pub-8638037215789792 was 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.

ConsentManager resolves consent through the User Messaging Platform and only then initialises the Ads SDK. MobileAds.initialize moved out of MyApp for two reasons: the SDK must not start before consent resolves, and showing the form needs an Activity context. Both activities build their AdView inside the consent callback.

No new dependency — user-messaging-platform:3.2.0 already arrives transitively via play-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.whenReady hook 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:

Old New
ADMOB_APPLICATION_ID ADMOB_APP_ID
BANNER_AD_ID ADMOB_BANNER_ID
INTERSTITIAL_AD_ID ADMOB_INTERSTITIAL_ID

This covers local.properties, the env fallbacks, the CI workflow, and the cppFlags macros consumed by native-lib.cpp.

Verified on a physical device

Scenario Result
Non-EEA (normal) IABTCF_gdprApplies=0, no dialog, banner loads
EEA, first launch Consent form appears before any ad
EEA, "Do not consent" IABTCF_PurposeConsents=00000000000 stored; banner still loads
Release without secrets Build fails naming all three
Release with new names Build succeeds
Release with old names Build fails — no silent fallback path remains

The 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. The gatherConsent KDoc 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

  • The interstitial stays deliberately unwired — highest invalid-traffic risk. ADMOB_ROADMAP.md records the frequency-cap design for when it is added, after banners have run clean for 2-4 weeks.
  • The GDPR message still needs publishing in the AdMob dashboard (Privacy & messaging → GDPR). Until then European users see Google's default test form rather than your own; the code is ready and needs no change.

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.
@welcome

welcome Bot commented Aug 24, 2026

Copy link
Copy Markdown

Thanks for opening this pull request! Please check out our contributing guidelines.

@eby-dev
eby-dev merged commit 9a04374 into master Aug 24, 2026
@welcome

welcome Bot commented Aug 24, 2026

Copy link
Copy Markdown

Congrats on merging your first pull request! We here at behaviorbot are proud of you!

@eby-dev
eby-dev deleted the feat/re-enable-admob branch August 24, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant