Your age-signal integration probably compiles, passes review, and does nothing

If you ship an Android app with ads, someone has probably asked you in the last few months whether you handle app-store age signals. If you have already done the work, this is about the specific way that work tends to fail silently.

And if you have not started, the timeline is not the one most people think.

The part that fails quietly

Google Play and, since iOS 26, Apple will tell your app roughly how old the person using it is. The obligation that follows is not to receive that signal. It is to act on it — which in practice means telling every advertising and analytics SDK in your build to stop profiling this user.

Each SDK has its own switch:

AdSettings.setMixedAudience(true);                          // Meta Audience Network
IronSource.setMetaData("is_child_directed", "true");        // ironSource / LevelPlay
MetaData md = new MetaData(context);                        // Unity Ads
md.set("user.nonbehavioral", true); md.commit();

Every one of these has the same requirement: it must be set before that SDK initialises. Set it afterwards and the call succeeds, returns normally, logs nothing, and has no effect on the session.

That is what makes it dangerous. The code is present. It is in the diff. A reviewer reads setMixedAudience(true) and ticks the box. Everything looks correct and nothing is.

Why "just call it first" is harder than it sounds

The obvious fix is to configure before you initialise:

public class MyApp extends Application {
    @Override public void onCreate() {
        super.onCreate();
        configureForMinor();   // surely this is early enough
        initialiseAdSdks();
    }
}

Application.onCreate feels like the beginning of your app. It is not the beginning of the process.

Android starts ContentProviders before Application.onCreate runs, and several SDKs ship one specifically so they can start themselves without you writing any code — FirebaseInitProvider, MobileAdsInitProvider and their equivalents. That is a convenience feature. It also means an SDK can already be running by the time your first line executes.

So whether your configuration lands in time depends on which SDKs auto-start, what they do on start, and in what order the providers ran — none of which is visible in your source. Two apps with identical-looking code can behave differently.

The remedies are known: disable the auto-init provider in your manifest and initialise explicitly, or pass configuration as manifest meta-data so it exists before any code runs. Which applies depends on the SDK.

The point is not that this is unfixable. It is that you cannot tell whether you have fixed it by reading the code.

The dates are not the ones you think

Most coverage of this treats it as an American problem starting in 2026. Two things are wrong with that.

Brazil got there first. The Digital Statute for Children and Adolescents (Lei 15.211/2025, "ECA Digital") has been enforceable since 17 March 2026, with no grace period and no phased rollout — months before Texas. Google Play began returning Brazilian age signals on the same date.

It also reaches further than the US laws. It applies to any provider of services targeted at minors or merely likely to be accessed by them, explicitly including companies based outside Brazil. "We don't market there" is not an answer if Brazilian teenagers can install your app.

And Brazil bans the thing Play sometimes gives you. ECA Digital requires reliable age verification and expressly prohibits mere self-declaration. Play's age response carries an assurance tier: TIER_A means the user typed their own age. TIER_D means government ID plus a selfie check.

So it is entirely possible to integrate Play Age Signals correctly, receive a TIER_A response, act on it faithfully, satisfy Texas — and still not satisfy Brazil, because the underlying assurance was self-declared. If your code treats all four tiers identically, that distinction is invisible to you.

The rest of the map, as at August 2026:

StatusNote
Brazil — ECA Digital in force
17 Mar 2026
reaches non-Brazilian companies; self-declaration prohibited
Texas — SB 2420 enforceable
under appeal
see the note below the table
Utah 6 May 2027 safe harbour for relying on store data; AG enforcement removed in 2026, private right of action remains
Louisiana 1 Jul 2027 delayed by HB 977 (Act 185, May 2026); no safe harbour
EU — DSA Article 28 in force
Feb 2024
see below
UK — Online Safety Act in force
25 Jul 2025
see below

Texas, in detail, because it is moving. The Fifth Circuit stayed the district court's injunction on 28 May 2026, and the law took effect 4 June 2026. The Supreme Court denied emergency applications to vacate that stay on 6 July 2026 — ruling on the stay alone, not on the merits. The Fifth Circuit then heard the First Amendment merits on an expedited schedule on 4 August 2026 and has not yet ruled. The law is enforceable today; whether it survives is not settled.

Two regimes that are already in force, and work differently

The EU has been stricter than any of this for two years. DSA Article 28 does not ask platforms to act on an age signal. It prohibits targeting minors with personalised advertising outright. That is a ban, not a duty, and it predates every US statute above.

The catch is scope: it binds "online platforms" as the DSA defines them — services that store and disseminate user content to the public. A social or user-generated-content app is very likely one. A single-player game with a banner ad is not. Work out which you are before assuming either way.

The UK's answer is the opposite of the American one. Age assurance duties under the Online Safety Act have applied since 25 July 2025, enforced by Ofcom, to user-to-user services, search services and services publishing pornography. Ofcom's accepted methods are photo-ID matching, facial age estimation, Open Banking, digital identity services and mobile-operator checks.

An app-store age signal is not on that list. Where Texas and Utah give you a safe harbour for relying on store data, the UK regime may not accept it as discharging the duty at all. One integration does not answer both questions.

What the API actually returns

If you have integrated Play Age Signals, it is worth knowing the shape of it, because the shape is where the mistake lives.

It is two calls, not one. requestAgeSignalsAccess() returns an AgeSignalsAccessResult carrying ageSignalsStatus — this is where NOT_SHARED (the user declined) and VERIFICATION_REQUIRED (verification pending) arrive. checkAgeSignals() returns an AgeSignalsResult carrying the ages.

That splits the failure in two, and the first half is the one nobody writes about. An app that only ever calls checkAgeSignals() cannot learn that the user declined to share at all — the status is not on that object. It reads null ages and carries on. The second half is the familiar one: an app that does make both calls but reads the ages without branching on the status treats "we don't know" as "no restriction", which is the unsafe default.

The result itself carries:

Failures arrive as AgeSignalsException.getErrorCode(). The code distinguishes "Play cannot answer right now" from "this app may not ask" — different problems, and an app that treats them alike is failing open on the second.

One thing that will bite you when you go looking. The library ships no consumer ProGuard rules. In a real shipping build I checked, the class names survived R8 intact while every accessor had been renamed to a single letter — ageLower() had become a(). That does not affect your app, which is compiled against the real names, but it does mean you cannot inspect a release build for these calls without the mapping file, and neither can anyone auditing you.

Google also recommends calling the Play Integrity API alongside, so a tampered build cannot simply fake an adult response.

For testing, Google ships FakeAgeSignalsManager in a testing artifact: you can simulate any of these states in unit and integration tests without a real minor account. It replaces the production manager, so it is for your own test suite rather than anything an outside auditor could use on a shipped build.

On iOS, Apple's counterpart is the DeclaredAgeRange framework, introduced in iOS 26 — AgeRangeService.requestAgeRange(...). Same obligations, different API, and it is easy to ship the Android half and forget the other one.

How would you know?

Two questions, and only one has a cheap answer.

"Which tracking SDKs are actually in my build?" That is answerable from the binary. So is "is the age-signals library in here at all?" — and that absence, if you find it, is definitive.

I wrote a scanner for this. It is free, open source, has no dependencies, and runs on your own machine — your build is never uploaded anywhere:

npx agesignals app-release.apk

It lists every advertising, analytics and attribution SDK compiled in, the restricted-mode call each one needs, and what that call must precede. It exits non-zero when something is missing, so it drops into CI.

"Is any of it wired correctly?" That is not answerable from the binary, and the scanner says so on every report. Whether checkAgeSignals() is called, in what order things initialise, whether the child-directed flag is actually on the outbound ad request — those need the app running.

A static tool can prove a library is absent. It cannot prove your integration works. Anything that tells you otherwise is selling you a certificate, not an answer.

What I would actually do

  1. Run the scanner on your current release build. It takes seconds and tells you what is in there.
  2. Check the ordering, per SDK, against its own documentation. This is where the silent failures live.
  3. Handle NOT_SHARED and VERIFICATION_REQUIRED explicitly, and do not let a null age fall through to the adult path.
  4. Look at the tier if Brazil is in scope for you.
  5. Work out which regimes apply before assuming one integration covers them all. The UK and EU answers are not the American one.
  6. Verify at runtime before telling anyone you are done — with a real session, not a code review.

None of this is difficult. It is just quiet, and quiet failures are the ones that reach production.

The scanner is at github.com/alnyc-hub/agesignals — Apache-2.0, no dependencies, nothing leaves your machine.

This is not legal advice. Statutory dates in this area have moved repeatedly; check them against primary sources before relying on any of them. Dates above were verified on 7 August 2026.