Skip to content

[Internal] Migrate Age Signals integration to SDK 0.0.4 - #16376

Merged
JorgeMucientes merged 15 commits into
issue/woomob-3766-age-verification-gatefrom
issue/woomob-3767-age-signals-sdk-004
Sep 2, 2026
Merged

JorgeMucientes merged 15 commits into
issue/woomob-3766-age-verification-gatefrom
issue/woomob-3767-age-signals-sdk-004

Conversation

@JorgeMucientes

@JorgeMucientes JorgeMucientes commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes WOOMOB-3767

⚠️ Do not merge. I'll merge all 4 stacked PRs together as a final working solution.

Description

Activates the two-stage Play Age Signals SDK 0.0.4 integration behind the existing feature flag.

  • Injects AgeSignalsManager behind the app-owned AgeSignalsClient boundary.
  • Supplies the resumed Activity to requestAgeSignalsAccess() and calls checkAgeSignals() only for SHARED.
  • Maps access, bounds, age-range source, and significant-change values into defensive domain enums without exposing install ID or approval date.
  • Treats a shared upper bound below 13 as conclusive even when the lower bound is missing, and distinguishes eligible ranges from genuinely ambiguous bounds while preserving prior authoritative restrictions.
  • Retries only the failing ACCESS or CHECK stage, with three total attempts across the request and 500 ms/1 s backoff; a CHECK retry never repeats the consent-capable ACCESS stage.
  • Removes SDK 0.0.3 userStatus handling.
  • Tests the real adapter with Google's FakeAgeSignalsManager plus explicit stage, gating, mapping, and retry coverage.

Stack: #16374 ← #16375 ← this PR. Merge bottom-up only after this activation layer is approved.

Test Steps

The manual-test patch replaces the production manager provided by Hilt with Google's SDK 0.0.4 FakeAgeSignalsManager. It is validated against commit 6038b3502a2c9287686a1f1db0dfbe0165c48b79, is only for local review, and must not be committed.

  1. Start from a clean checkout of this PR, then download and apply the patch:

    patch_file=/tmp/pr-16376-fake-age-signals.patch
    gh api repos/woocommerce/woocommerce-android/issues/comments/5413900251 --jq .body > "$patch_file" &&
    git apply --check "$patch_file" &&
    git apply "$patch_file"
  2. Modify the value for ACTIVE_MANUAL_AGE_SIGNALS_SCENARIO in ManualAgeSignalsTestScenario.kt with the below values then install the Wasabi debug build, and cold-start both logged-in and logged-out flows:

    ./gradlew :WooCommerce:installWasabiDebug
  3. Cover the access-stage and recovery scenarios:

    Scenario Expected result
    NOT_SHARED Do not show a gate or log out. Retain a prior restriction; otherwise allow. The app's automated adapter test proves checkAgeSignals() is not called.
    VERIFICATION_REQUIRED Keep the non-cancelable verification dialog visible. Retry remains recoverable and does not log out or reset the selected site.
    VERIFICATION_REQUIRED_THEN_SHARED_18_PLUS First request shows the verification dialog. Open Play and return; exactly one resume retry gets SHARED, checks the adult result, clears a prior restriction, and dismisses the dialog.
    UNEXPECTED_ACCESS_STATUS Treat separately from NOT_SHARED: preserve a prior restriction; otherwise allow without crashing.
  4. Cover shared age ranges and significant changes:

    Scenario Expected result
    SHARED_BELOW_13 Persist BELOW_MINIMUM_AGE, log out, and show the terminal restriction message.
    SHARED_13_TO_15, SHARED_16_TO_17, or SHARED_18_PLUS Allow and clear a prior authoritative restriction.
    SHARED_MISSING_LOWER_BELOW_13 Persist BELOW_MINIMUM_AGE, log out, and show the terminal restriction message.
    SHARED_13_PLUS_OPEN_ENDED Allow and clear a prior authoritative restriction as a conclusive eligible range.
    SHARED_MISSING_BOUNDS or SHARED_CROSSING_BOUNDS Preserve a prior restriction; otherwise allow as an ambiguous, non-authoritative result.
    SIGNIFICANT_CHANGE_PENDING, SIGNIFICANT_CHANGE_APPROVED, or SIGNIFICANT_CHANGE_DECLINED Map the status but do not add an app-wide restriction.
  5. Cover failures and retries:

    Scenario Expected result
    ACCESS_NETWORK_ERROR or CHECK_NETWORK_ERROR Terminal for this run: preserve a prior restriction; otherwise allow.
    ACCESS_TRANSIENT_ERROR or CHECK_TRANSIENT_ERROR Retry only the failing stage within the shared bounded budget, then preserve a prior restriction or otherwise allow when the fake continues returning the transient failure.
  6. To verify preservation and clearing, first run SHARED_BELOW_13; then change only the scenario and reinstall over the app without clearing data. Ambiguous, non-shared, unknown, and failure scenarios must retain the restriction; a conclusive 13+ shared scenario must clear it.

  7. Restore the scenario to the patch default (VERIFICATION_REQUIRED_THEN_SHARED_18_PLUS), then remove the manual patch:

    git apply --check --reverse "$patch_file"
    git apply --reverse "$patch_file"
    git status --short

The scenarios use the same setNextAgeSignalsAccessResult, setNextAgeSignalsResult, and exception setters documented in Google's Age Signals testing guide. The verification-to-shared scenario composes two fake managers so both stages can be exercised in one app run.

Play-installed internal-testing QA is still required separately for acceptance; the fake patch is deterministic integration/review support and does not validate Play ownership, account configuration, or the real Play UI.

Images/gif

No visual changes in this stack layer; the verification UI is provided by #16375.

  • I have considered if this change warrants release notes and deferred them to the final telemetry and release-validation PR in this stack.

@JorgeMucientes JorgeMucientes added this to the 25.4 milestone Aug 6, 2026
@JorgeMucientes JorgeMucientes added type: task An internally driven task. status: do not merge Dependent on another PR, ready for review but not ready for merge. priority: high Affects lots of customers substantially, but not critically. status: feature-flagged Behind a feature flag. Milestone is not strongly held. org-team: Kiwi Kiwi team issues dependencies Pull requests that update a dependency file labels Aug 6, 2026
@dangermattic

dangermattic commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator
1 Error
🚫 Please ensure the PR has the category: tracks label.
1 Warning
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
1 Message
📖

This PR contains changes to Tracks-related logic. Please ensure (author and reviewer) the following are completed:

  • The tracks events must be validated in the Tracks system.
  • Verify the internal Tracks spreadsheet has also been updated.
  • Please consider registering any new events.
  • The PR must be assigned the category: tracks label.

Generated by 🚫 Danger

@wpmobilebot

Copy link
Copy Markdown
Collaborator

Project dependencies changes

list
! Upgraded Dependencies
com.google.android.play:age-signals:0.0.4, (changed from 0.0.3)
tree
-\--- com.google.android.play:age-signals:0.0.3
+\--- com.google.android.play:age-signals:0.0.4

@wpmobilebot

wpmobilebot commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

Project manifest changes for WooCommerce

The following changes in the WooCommerce's merged AndroidManifest.xml file were detected (build variant: vanillaRelease):

--- ./build/reports/diff_manifest/WooCommerce/vanillaRelease/base_manifest.txt	2026-08-28 10:00:25.054915841 +0000
+++ ./build/reports/diff_manifest/WooCommerce/vanillaRelease/head_manifest.txt	2026-08-28 10:00:27.789845993 +0000
@@ -665,6 +665,11 @@
             android:name="com.google.android.gms.common.api.GoogleApiActivity"
             android:exported="false"
             android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+        <activity
+            android:name="com.google.android.play.agesignals.AgeSharingConsentWrapperActivity"
+            android:exported="false"
+            android:stateNotNeeded="true"
+            android:theme="@style/Theme.Play.AgeSignals.Transparent" />
 
         <service
             android:name="androidx.work.impl.background.systemjob.SystemJobService"

Go to https://buildkite.com/automattic/woocommerce-android/builds/41912/canvas?sid=01a047ce-71e2-4c4e-8eb2-6c613c1408d7, click on the Artifacts tab and audit the files.

@wpmobilebot

wpmobilebot commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

App Icon📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App NameWooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Build Number780
Version25.5-rc-1
Application IDcom.woocommerce.android.prealpha
Commit8cd2218
Installation URL544glc0sbbjko
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

Copy link
Copy Markdown
Collaborator

Version 25.4 has now entered code-freeze, so the milestone of this PR has been updated to 25.5.

@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3766-age-verification-gate branch from 02f91f2 to af027a0 Compare August 19, 2026 10:50
@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3767-age-signals-sdk-004 branch 2 times, most recently from 8e9c3ce to 722ba97 Compare August 19, 2026 11:12
@JorgeMucientes JorgeMucientes modified the milestones: 25.5, 25.6 Aug 19, 2026
@JorgeMucientes
JorgeMucientes marked this pull request as ready for review August 19, 2026 15:12
@JorgeMucientes
JorgeMucientes requested a review from a team as a code owner August 19, 2026 15:12
@JorgeMucientes
JorgeMucientes requested review from irfano and removed request for a team August 19, 2026 15:12

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review - Found 1 potential issue

The SDK 0.0.4 migration is well structured: the two-stage access/check flow, bounded retry with backoff, and defensive mapping of unknown SDK values to UNEXPECTED are all solid, and the test coverage (retry budget, virtual-time backoff assertions, defensive mapping) is thorough. One behavior-change concern is noted inline on the evaluator.

PR housekeeping
  • Applied repo AGENTS.md/CLAUDE.md guidance while reviewing (Store Management MVVM conventions).

Automatic review · claude-opus-4-8 · Workflow run

How to reply to a finding

Reply on this review (or inline at the line the finding refers to) with one of:

  • @claude addressed - I made the change. Bot verifies against the next diff before marking resolved.
  • @claude rejected: <reason> - Will not fix; reason gets quoted on the next review.
  • @claude not-applicable - Finding does not apply (wrong file, already covered elsewhere, etc.).

The bot honours these on the next review pass.

@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3767-age-signals-sdk-004 branch from 722ba97 to eaf575b Compare August 19, 2026 15:52
@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3766-age-verification-gate branch from 420552e to 1f3bf26 Compare August 19, 2026 15:52
@JorgeMucientes

This comment was marked as resolved.

@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3766-age-verification-gate branch from 1f3bf26 to e12d776 Compare August 26, 2026 06:52
@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3767-age-signals-sdk-004 branch from eaf575b to f3ab0ed Compare August 26, 2026 06:55
@AdamGrzybkowski AdamGrzybkowski self-assigned this Aug 26, 2026
@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3767-age-signals-sdk-004 branch from f3ab0ed to 37a19e6 Compare August 27, 2026 08:38

@AdamGrzybkowski AdamGrzybkowski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 👍 There's one thing worth addressing.


return when {
ageLower >= WOOCOMMERCE_TOS_MINIMUM_AGE_FOR_APP_USE -> authoritativeAllowed()
else -> nonAuthoritative(priorRestriction)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the range straddles the threshold (ageLower < 13 ≤ ageUpper), it returns nonAuthoritative → Allowed if there’s no prior restriction.

A test like this in AgeEligibilityEvaluatorTest shows this:

@Test
    fun `given an ambiguous shared age range straddling 13 without a prior restriction, when evaluated, then access is allowed non-authoritatively`() {
        listOf(
            sharedResult(ageLower = 12, ageUpper = 15),
            sharedResult(ageLower = 0, ageUpper = 13),
            sharedResult(ageLower = 10, ageUpper = null)
        ).forEach { result ->
            val evaluation = evaluate(result)

            assertThat(evaluation).isEqualTo(
                AgeEligibilityEvaluation(AgeEligibilityDecision.Allowed, isAuthoritative = false)
            )
        }
    }

None of the ranges above are conclusive, right?

@JorgeMucientes JorgeMucientes Aug 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @AdamGrzybkowski just trying to better understand your point. If your question/idea is along the lines of:

Are we intentionally failing open when the signal says the user might be under 13, but doesn’t prove it?

If so, then yes, it's intentional. While this might seem too relaxed, in practice it won't be. Google’s API default ranges are 0–12, 13–15, 16–17, and 18+, so a normal response cannot straddle 13 (our min required age). A straddling range would require us to configure custom ranges in Google Play Console, which we currently have no reason or plan to do.

This logic is therefore defensive handling for an unexpected or future configuration and I agree that a test for this could be useful to document that fallback explicitly, I'll add it.

EDIT: Pushed here 6038b35

data class AgeSignalsRequestResult(
val accessStatus: AgeSignalsAccessStatus,
val ageSignals: SharedAgeSignals? = null,
val retryCount: Int = 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retryCount values seem never to be used - do we need it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used in this PR that I'll set to ready for review in a couple hours 🙂

@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3767-age-signals-sdk-004 branch from 6306633 to 6038b35 Compare August 28, 2026 09:58
@JorgeMucientes
JorgeMucientes force-pushed the issue/woomob-3766-age-verification-gate branch from 08d1467 to 2d54fbb Compare August 28, 2026 09:58

@AdamGrzybkowski AdamGrzybkowski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation and the changes! :shipit:

@JorgeMucientes JorgeMucientes removed the status: do not merge Dependent on another PR, ready for review but not ready for merge. label Sep 2, 2026
@JorgeMucientes
JorgeMucientes merged commit c99a6ff into issue/woomob-3766-age-verification-gate Sep 2, 2026
5 of 7 checks passed
@JorgeMucientes
JorgeMucientes deleted the issue/woomob-3767-age-signals-sdk-004 branch September 2, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file org-team: Kiwi Kiwi team issues priority: high Affects lots of customers substantially, but not critically. status: feature-flagged Behind a feature flag. Milestone is not strongly held. type: task An internally driven task.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants