Skip to content

Conversation

@JorgeMucientes
Copy link
Contributor

@JorgeMucientes JorgeMucientes commented Dec 11, 2025

Part of WOOMOB-1859

⚠️ Do not merge until we figure out were exactly this changes must be added (hotfix, betafix?)

Description

This PR integrates Play Age Signals API in Woo Android.

The added logic works as follows:

  • Every time the app is opened (after being previously killed) we'll check the Google Age Signal API values to verify if the user is still eligible to use the app.
  • If the user is ineligible (more on when this could happen) the we'll log the user out and show a dialog that the access to the app is restricted for them based on their age.

When is a user ineligible to use WooCommerce app? Discussion p2y3YZ-anO-p2#comment-24048.
According to WooCommerce TOS our app usage is expected for 13+ years old users:

All of our apps have a required age of 13+ and so we would need the app stores to return a result showing the user is at least that age. Parents can also revoke consent for a minor in their family account; the API should be capable of informing us of such revocations and we’ll need to figure out how to honor those.

With the TOS in mind the logic to determine if a user is eligible will be based on the 4 different user statuses the API provides:

Screenshot 2025-12-12 at 18 11 47
  • Verified users will be granted access bu default
  • Supervised users above +13 plus we'll be granted access by default.
  • Supervised users whose access was denied will be log out and access restricted via a blocking dialog. See screen recording below.
  • Any other case were we fail to retrieve the userStatus, we'll grant access by default.

Here's a diagram to help better understand the different flows:

Screenshot 2025-12-22 at 17 37 32

Test Steps

We currently can't test in prod, because we app needs to be downloaded from Google Play in order for the API to work. For that Google provides a FakeAgeSignalsManager() that enables us to test the different scenarios.

Apply the following patch in order to simulate the different API responses we'll get from the Play Signals API:

Use_FakeAgeSignalsManager_to_simulate_supervised_users.patch

For each of the following tests you'll need to open AgeSignalsClient.kt. and update the mocked userStatus

Test Case 1: User 18+ (Verified)

  • Set userStatus to VERIFIED
  • Open the app
  • Log in as usual

Test Case 2: Approved 13+ user (Supervised)

  • Set userStatus to SUPERVISED
  • Set ageUpper to 14 (use setAgeUpper(14))
  • Open the app
  • Log in as usual

Test Case 3: Supervised user under 13

  • Set userStatus to SUPERVISED_APPROVAL_PENDING
  • Set ageUpper to 12
  • Open the app
  • Logged out and restricted access for being below 13+ years old

Test Case 4: Supervised user approval denied

  • Set userStatus to SUPERVISED_APPROVAL_DENIED
  • No matter what the age is.
  • Open the app
  • You'll be logged out and a dialog displayed saying you Google account is restricted from using the app.

Images/gif

Screen_recording_20251219_165448.mp4
  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

@dangermattic
Copy link
Collaborator

dangermattic commented Dec 11, 2025

1 Error
🚫 This PR is tagged with status: do not merge label(s).
1 Warning
⚠️ Class GoogleAgeSignalsClient is missing tests, but unit-tests-exemption label was set to ignore this.
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
Collaborator

wpmobilebot commented Dec 11, 2025

Project dependencies changes

list
+ New Dependencies
com.google.android.play:age-signals:0.0.2

! Upgraded Dependencies
com.google.android.gms:play-services-basement:18.9.0, (changed from 18.5.0)
tree
 +--- com.google.firebase:firebase-messaging -> 25.0.1
 |    +--- com.google.firebase:firebase-common:22.0.1
 |    |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.9.0 -> 1.10.2
 |    |    |    \--- com.google.android.gms:play-services-tasks:16.0.1 -> 18.2.0
-|    |    |         \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0
+|    |    |         \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0
-|    |    \--- com.google.android.gms:play-services-basement:18.3.0 -> 18.5.0 (*)
+|    |    \--- com.google.android.gms:play-services-basement:18.3.0 -> 18.9.0 (*)
 |    +--- com.google.firebase:firebase-iid-interop:17.1.0
-|    |    \--- com.google.android.gms:play-services-basement:17.0.0 -> 18.5.0 (*)
+|    |    \--- com.google.android.gms:play-services-basement:17.0.0 -> 18.9.0 (*)
 |    +--- com.google.firebase:firebase-measurement-connector:19.0.0
-|    |    \--- com.google.android.gms:play-services-basement:17.0.0 -> 18.5.0 (*)
+|    |    \--- com.google.android.gms:play-services-basement:17.0.0 -> 18.9.0 (*)
 |    +--- com.google.android.gms:play-services-base:18.1.0 -> 18.5.0
-|    |    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    |    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
-|    +--- com.google.android.gms:play-services-basement:18.3.0 -> 18.5.0 (*)
+|    +--- com.google.android.gms:play-services-basement:18.3.0 -> 18.9.0 (*)
 |    +--- com.google.android.gms:play-services-cloud-messaging:17.2.0
-|    |    \--- com.google.android.gms:play-services-basement:18.3.0 -> 18.5.0 (*)
+|    |    \--- com.google.android.gms:play-services-basement:18.3.0 -> 18.9.0 (*)
 |    \--- com.google.android.gms:play-services-stats:17.0.2
-|         \--- com.google.android.gms:play-services-basement:18.0.0 -> 18.5.0 (*)
+|         \--- com.google.android.gms:play-services-basement:18.0.0 -> 18.9.0 (*)
 +--- com.google.firebase:firebase-config -> 23.0.1
 |    \--- com.google.firebase:firebase-abt:21.1.1
-|         \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.5.0 (*)
+|         \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.9.0 (*)
 +--- com.google.firebase:firebase-analytics -> 23.0.0
 |    +--- com.google.android.gms:play-services-measurement:23.0.0
 |    |    +--- com.google.android.gms:play-services-ads-identifier:18.0.0
-|    |    |    \--- com.google.android.gms:play-services-basement:18.0.0 -> 18.5.0 (*)
+|    |    |    \--- com.google.android.gms:play-services-basement:18.0.0 -> 18.9.0 (*)
-|    |    +--- com.google.android.gms:play-services-basement:18.5.0 (*)
+|    |    +--- com.google.android.gms:play-services-basement:18.5.0 -> 18.9.0 (*)
 |    |    +--- com.google.android.gms:play-services-measurement-base:23.0.0
-|    |    |    \--- com.google.android.gms:play-services-basement:18.5.0 (*)
+|    |    |    \--- com.google.android.gms:play-services-basement:18.5.0 -> 18.9.0 (*)
 |    |    +--- com.google.android.gms:play-services-measurement-impl:23.0.0
-|    |    |    \--- com.google.android.gms:play-services-basement:18.5.0 (*)
+|    |    |    \--- com.google.android.gms:play-services-basement:18.5.0 -> 18.9.0 (*)
 |    |    \--- com.google.android.gms:play-services-measurement-sdk-api:23.0.0
-|    |         \--- com.google.android.gms:play-services-basement:18.5.0 (*)
+|    |         \--- com.google.android.gms:play-services-basement:18.5.0 -> 18.9.0 (*)
 |    +--- com.google.android.gms:play-services-measurement-api:23.0.0
-|    |    \--- com.google.android.gms:play-services-basement:18.5.0 (*)
+|    |    \--- com.google.android.gms:play-services-basement:18.5.0 -> 18.9.0 (*)
 |    \--- com.google.android.gms:play-services-measurement-sdk:23.0.0
-|         \--- com.google.android.gms:play-services-basement:18.5.0 (*)
+|         \--- com.google.android.gms:play-services-basement:18.5.0 -> 18.9.0 (*)
 +--- com.google.android.gms:play-services-auth:21.4.0
 |    +--- com.google.android.gms:play-services-auth-api-phone:18.0.2
-|    |    \--- com.google.android.gms:play-services-basement:18.0.2 -> 18.5.0 (*)
+|    |    \--- com.google.android.gms:play-services-basement:18.0.2 -> 18.9.0 (*)
 |    +--- com.google.android.gms:play-services-auth-base:18.0.10
-|    |    \--- com.google.android.gms:play-services-basement:18.2.0 -> 18.5.0 (*)
+|    |    \--- com.google.android.gms:play-services-basement:18.2.0 -> 18.9.0 (*)
-|    +--- com.google.android.gms:play-services-basement:18.5.0 (*)
+|    +--- com.google.android.gms:play-services-basement:18.5.0 -> 18.9.0 (*)
 |    \--- com.google.android.gms:play-services-fido:20.0.1 -> 21.0.0
-|         \--- com.google.android.gms:play-services-basement:18.3.0 -> 18.5.0 (*)
+|         \--- com.google.android.gms:play-services-basement:18.3.0 -> 18.9.0 (*)
++--- com.google.android.play:age-signals:0.0.2
+|    +--- com.google.android.gms:play-services-basement:18.9.0 (*)
+|    +--- com.google.android.gms:play-services-tasks:18.2.0 (*)
+|    \--- com.google.android.play:core-common:2.0.4
 +--- project :libs:login
 |    \--- androidx.credentials:credentials-play-services-auth:1.5.0
 |         +--- com.google.android.gms:play-services-auth-blockstore:16.4.0
-|         |    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|         |    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 |         \--- com.google.android.gms:play-services-identity-credentials:16.0.0-alpha02
-|              \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|              \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 +--- project :libs:cardreader
 |    \--- com.stripe:stripeterminal-taptopay:4.7.5
 |         \--- com.google.android.play:integrity:1.1.0
-|              \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.5.0 (*)
+|              \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.9.0 (*)
 +--- com.google.android.play:app-update:2.1.0
-|    \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.5.0 (*)
+|    \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.9.0 (*)
 +--- com.google.android.play:review:2.0.2
-|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 +--- com.google.android.gms:play-services-code-scanner:16.1.0
-|    +--- com.google.android.gms:play-services-basement:18.1.0 -> 18.5.0 (*)
+|    +--- com.google.android.gms:play-services-basement:18.1.0 -> 18.9.0 (*)
 |    \--- com.google.mlkit:barcode-scanning-common:17.0.0
-|         +--- com.google.android.gms:play-services-basement:18.0.0 -> 18.5.0 (*)
+|         +--- com.google.android.gms:play-services-basement:18.0.0 -> 18.9.0 (*)
 |         \--- com.google.mlkit:vision-common:17.0.0 -> 17.3.0
-|              +--- com.google.android.gms:play-services-basement:18.1.0 -> 18.5.0 (*)
+|              +--- com.google.android.gms:play-services-basement:18.1.0 -> 18.9.0 (*)
 |              \--- com.google.mlkit:common:18.6.0 -> 18.11.0
-|                   \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|                   \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 +--- com.google.mlkit:text-recognition:16.0.1
-|    +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 |    +--- com.google.android.gms:play-services-mlkit-text-recognition:19.0.1
-|    |    +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    |    +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 |    |    \--- com.google.android.gms:play-services-mlkit-text-recognition-common:19.1.0
-|    |         +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    |         +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 |    |         \--- com.google.mlkit:vision-interfaces:16.3.0
-|    |              \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.5.0 (*)
+|    |              \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.9.0 (*)
 |    \--- com.google.mlkit:text-recognition-bundled-common:17.0.0
-|         \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|         \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 +--- com.google.android.gms:play-services-mlkit-text-recognition-japanese:16.0.1
-|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 +--- com.google.android.gms:play-services-mlkit-text-recognition-chinese:16.0.1
-|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 +--- com.google.android.gms:play-services-mlkit-text-recognition-korean:16.0.1
-|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 +--- com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1
-|    \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.5.0 (*)
+|    \--- com.google.android.gms:play-services-basement:18.1.0 -> 18.9.0 (*)
 +--- com.google.mlkit:barcode-scanning:17.3.0
-|    +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 |    \--- com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1
-|         \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|         \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)
 +--- com.google.android.gms:play-services-wearable:19.0.0
-|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
+|    \--- com.google.android.gms:play-services-basement:18.4.0 -> 18.9.0 (*)

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Dec 11, 2025

📲 You can test the changes from this Pull Request in WooCommerce-Wear Android by scanning the QR code below to install the corresponding build.
App NameWooCommerce-Wear Android
Platform⌚️ Wear OS
FlavorJalapeno
Build TypeDebug
Commit986f3b4
Direct Downloadwoocommerce-wear-prototype-build-pr15085-986f3b4.apk

Refactor the legacy viewmodel implementation that was previously added to LoginActivity
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Dec 11, 2025

🤖 Test Failure Analysis

Your tests failed. Claude has analyzed the failures - check the annotation for details.

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Dec 11, 2025

📲 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
Commit986f3b4
Direct Downloadwoocommerce-prototype-build-pr15085-986f3b4.apk

@JorgeMucientes JorgeMucientes modified the milestones: 23.8 ❄️, 23.9 Dec 12, 2025
@JorgeMucientes JorgeMucientes marked this pull request as ready for review December 12, 2025 18:22
@JorgeMucientes
Copy link
Contributor Author

I'm moving this to draft as I need to add bigger changes than I expected. I'll let you know once everything is ready again @irfano. And thanks for the first round 👍🏼

@JorgeMucientes JorgeMucientes marked this pull request as draft December 18, 2025 18:25
These changes ensure the restricted access dialog is shown or hide based on any changes happening to age eligibility checks
@JorgeMucientes JorgeMucientes added the status: do not merge Dependent on another PR, ready for review but not ready for merge. label Dec 19, 2025
@JorgeMucientes JorgeMucientes added the category: tracks Related to analytics, including Tracks Events. label Dec 19, 2025
@JorgeMucientes
Copy link
Contributor Author

Hey @irfano this is ready for another round 🙏🏼

  • Applied all of your small suggested changes
  • Addressed the main bug you reported by making the age eligibility status observable
  • Added tracking so we can figure out the results the API returns and how often this dialog is shown \
  • Some other minor refactor in AgeEligibilityChecker to avoid unnecessary checks

It's worth adding RELEASE-NOTES for this update.

I haven't added release notes yet as I'm still not sure where these changes are going to land (23.8.1 or 23.9)

@JorgeMucientes JorgeMucientes marked this pull request as ready for review December 19, 2025 16:34
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Is the singleton annotation needed here? From what I understand we inject the checker just once in the initializer. If we declare it as singleton, it'll never be garbage collected. It's a minor memory optimization, but unless we need it I'd personally replace it with @Reusable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point @malinajirka. However, AgeEligibilityChecker.kt is injected in several places were we subscribe to its state like LoginActivity.kt or MainActivityViewModel.kt. Removing the @Singleton would mean I'll need to save and expose the state in an observable DataStore. This is not a big deal but I believe it's overkill for this feature.

It's a minor memory optimization, but unless we need it I'd personally replace it with @reusable.

Curious about that. Given AgeEligibilityChecker is injected in AppInitializer class which afaik will survive as long as the app is not killed, setting AgeEligibilityChecker as a singleton will prevent the app from creating multiple instances of the same class, were as @Resuable wouldn't guarantee that. So technically, in terms of memory usage, @reusable could be worse?

Copy link
Contributor

Choose a reason for hiding this comment

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

Curious about that. Given AgeEligibilityChecker is injected in AppInitializer class which afaik will survive as long as the app is not killed

Good point, I didn't realize we hold onto the instance in AppInitializer. So it doesn't really matter which one we use.

setting AgeEligibilityChecker as a singleton will prevent the app from creating multiple instances of the same class, were as @Resuable wouldn't guarantee that.

Reusable annotation also guarantees maximum one instance at a time, but it can be garbage collected and re-created when needed later. However, as you correctly pointed out, we reference it so we never let the GC to collect it anyway.

Copy link
Contributor

@malinajirka malinajirka left a comment

Choose a reason for hiding this comment

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

Thanks @JorgeMucientes! Overall looks good to me. I've left some comments with suggestions + questions + one potential bug.

Copy link
Contributor

@irfano irfano left a comment

Choose a reason for hiding this comment

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

The incorrect “previous app state” issue is fixed now. 👍🏻 Thanks for the change. I added more comments that needs your attention. Also some reminders

  • RELEASE-NOTES for the change,
  • Register new tracks

When user access is restricted due to being under 13 years old (our declared minimum age in Woo TOS) we need to display a different error message stating this.
@JorgeMucientes
Copy link
Contributor Author

Hey @irfano thanks for your thorough review 🙏🏼
I've addressed all of your feedback in the last couple commits. Hopefully this will leave the implementation ready to merge once we decide (after January 1) wether this is released as a hotfix or as regular release.

RELEASE-NOTES for the change,

As mentioned above the reason I haven't added release notes yet is because I'm unsure which app version will be the one including these changes in the end.

Register new tracks

I'll proceed and register those events now. Great reminder!

Copy link
Contributor

@irfano irfano left a comment

Choose a reason for hiding this comment

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

As mentioned above the reason I haven't added release notes yet is because I'm unsure which app version will be the one including these changes in the end.

Sorry for unnecessary reminder. I missed your previous comment.

LGTM! 👍🏻 Thanks for staying on top of this.

@JorgeMucientes JorgeMucientes modified the milestones: 23.9, 24.0 Jan 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: tracks Related to analytics, including Tracks Events. status: do not merge Dependent on another PR, ready for review but not ready for merge. unit-tests-exemption woocommerce-android

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants