-
Notifications
You must be signed in to change notification settings - Fork 138
Issue/woomob 1859 declared age range apis for app stores #15085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 27 commits
08f0323
6a60107
f680250
4d566b1
cc805e4
ffbb702
49c3021
a190803
c821da4
47cb999
61c5e09
e4c033b
5b3a3f7
805fb39
6fd2512
c881c76
02abd20
df33859
a2b9a02
904429e
fdf7ad7
6b052c2
645e704
eddcc4a
c6f2983
8d765d9
f71eeb7
66ca6a6
e3520e0
1600031
4476fa4
1e0843f
31dfd3c
2715066
d5e76dc
3788e0d
986f3b4
fd93c49
b9f9b18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package com.woocommerce.android.ui.ageeligibility | ||
|
|
||
| import com.google.android.gms.common.api.ApiException | ||
| import com.google.android.play.agesignals.model.AgeSignalsVerificationStatus | ||
| import com.woocommerce.android.AppPrefsWrapper | ||
| import com.woocommerce.android.analytics.AnalyticsEvent | ||
| import com.woocommerce.android.analytics.AnalyticsTrackerWrapper | ||
| import com.woocommerce.android.ui.login.AccountRepository | ||
| import com.woocommerce.android.util.WooLog | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.StateFlow | ||
| import kotlinx.coroutines.flow.asStateFlow | ||
| import javax.inject.Inject | ||
| import javax.inject.Singleton | ||
|
|
||
| @Singleton | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Curious about that. Given AgeEligibilityChecker is injected in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good point, I didn't realize we hold onto the instance in AppInitializer. So it doesn't really matter which one we use.
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. |
||
| class AgeEligibilityChecker @Inject constructor( | ||
| private val client: AgeSignalsClient, | ||
| private val prefsWrapper: AppPrefsWrapper, | ||
| private val accountRepository: AccountRepository, | ||
| private val trackerWrapper: AnalyticsTrackerWrapper | ||
| ) { | ||
|
|
||
| private val _isUserAgeRangeEligible = MutableStateFlow(prefsWrapper.isUserAgeEligibleForAppUse) | ||
| val isUserAgeRangeEligible: StateFlow<Boolean> = _isUserAgeRangeEligible.asStateFlow() | ||
|
|
||
| suspend fun checkAge() { | ||
| val trackingProperties = mutableMapOf<String, Any>() | ||
| try { | ||
| val result = client.checkAge() | ||
| processAgeCheck(result.userStatus, result.ageUpper) | ||
| trackingProperties["retrieved_age"] = result.ageUpper ?: -1 | ||
| trackingProperties["user_status"] = getUserStatusAsString(result.userStatus) | ||
| } catch (exception: ApiException) { | ||
JorgeMucientes marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| WooLog.i( | ||
| WooLog.T.UTILS, | ||
| "AgeEligibilityChecker ${exception.javaClass.simpleName} while checking user " + | ||
| "age: ${exception.message}, reverting user eligibility to true" | ||
| ) | ||
| prefsWrapper.isUserAgeEligibleForAppUse = true | ||
| _isUserAgeRangeEligible.value = true | ||
| } | ||
| if (isUserAgeRangeEligible.value.not()) { | ||
| accountRepository.logout() | ||
| } | ||
| trackingProperties["access_restricted"] = _isUserAgeRangeEligible.value | ||
JorgeMucientes marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| trackerWrapper.track(AnalyticsEvent.ACCOUNT_AGE_RESTRICTION_CHECKED, properties = trackingProperties) | ||
| } | ||
|
|
||
| private fun processAgeCheck(userStatus: Int?, ageUpper: Int?) { | ||
| val isUserAgeEligible = when (userStatus) { | ||
| AgeSignalsVerificationStatus.VERIFIED -> true | ||
| AgeSignalsVerificationStatus.SUPERVISED, | ||
| AgeSignalsVerificationStatus.SUPERVISED_APPROVAL_PENDING -> { | ||
| if (ageUpper == null) { | ||
| true // If we can't determine the age return true | ||
| } else { | ||
| ageUpper >= WOOCOMMERCE_TOS_MINIMUM_AGE_FOR_APP_USE | ||
JorgeMucientes marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| AgeSignalsVerificationStatus.SUPERVISED_APPROVAL_DENIED -> false | ||
|
|
||
| AgeSignalsVerificationStatus.UNKNOWN -> true // Safe default: allow access if unknown | ||
| else -> true // Handle any other cases as default | ||
| } | ||
|
|
||
| prefsWrapper.isUserAgeEligibleForAppUse = isUserAgeEligible | ||
| _isUserAgeRangeEligible.value = isUserAgeEligible | ||
| } | ||
|
|
||
| private fun getUserStatusAsString(userStatus: Int?): String { | ||
| return when (userStatus) { | ||
| AgeSignalsVerificationStatus.VERIFIED -> "VERIFIED" | ||
| AgeSignalsVerificationStatus.SUPERVISED -> "SUPERVISED" | ||
| AgeSignalsVerificationStatus.SUPERVISED_APPROVAL_PENDING -> "SUPERVISED_APPROVAL_PENDING" | ||
| AgeSignalsVerificationStatus.SUPERVISED_APPROVAL_DENIED -> "SUPERVISED_APPROVAL_DENIED" | ||
| AgeSignalsVerificationStatus.UNKNOWN -> "UNKNOWN" | ||
| else -> "UNKNOWN" | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| private const val WOOCOMMERCE_TOS_MINIMUM_AGE_FOR_APP_USE = 13 | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.woocommerce.android.ui.ageeligibility | ||
|
|
||
| import android.content.Context | ||
| import com.google.android.play.agesignals.AgeSignalsManagerFactory | ||
| import com.google.android.play.agesignals.AgeSignalsRequest | ||
| import dagger.hilt.android.qualifiers.ApplicationContext | ||
| import kotlinx.coroutines.tasks.await | ||
| import javax.inject.Inject | ||
| import javax.inject.Singleton | ||
|
|
||
| interface AgeSignalsClient { | ||
| suspend fun checkAge(): AgeCheckResult | ||
| } | ||
|
|
||
| data class AgeCheckResult( | ||
| val userStatus: Int?, | ||
| val ageUpper: Int? | ||
| ) | ||
JorgeMucientes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Singleton | ||
JorgeMucientes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| class DefaultAgeSignalsClient @Inject constructor( | ||
| @ApplicationContext private val context: Context | ||
| ) : AgeSignalsClient { | ||
| override suspend fun checkAge(): AgeCheckResult { | ||
| val manager = AgeSignalsManagerFactory.create(context) | ||
| val result = manager.checkAgeSignals(AgeSignalsRequest.builder().build()).await() | ||
| return AgeCheckResult(result.userStatus(), result.ageUpper()) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.woocommerce.android.ui.ageeligibility | ||
|
|
||
| import dagger.Binds | ||
| import dagger.Module | ||
| import dagger.hilt.InstallIn | ||
| import dagger.hilt.components.SingletonComponent | ||
| import javax.inject.Singleton | ||
|
|
||
| @Module | ||
| @InstallIn(SingletonComponent::class) | ||
| abstract class AgeSignalsModule { | ||
| @Binds | ||
| @Singleton | ||
| abstract fun bindAgeSignalsClient(impl: DefaultAgeSignalsClient): AgeSignalsClient | ||
| } |
This comment was marked as outdated.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.