Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,10 @@ License.
<string name="personalid_configuration_process_failed_title">Échec du processus</string>
<string name="personalid_configuration_process_failed_subtitle">Votre appareil n\'est pas compatible avec PersonalID pour le moment. Veuillez réessayer sur un autre appareil.</string>
<string name="personalid_configuration_process_failed_security_subtitle">Votre appareil n\'est pas compatible avec PersonalID pour le moment en raison de l\'indisponibilité de la fonctionnalité de sécurité. Veuillez réessayer sur un autre appareil.</string>
<string name="personalid_configuration_process_failed_play_services">Veuillez vous assurer que le Play Store est disponible sur votre appareil et qu\'il est à jour.</string>
<string name="personalid_configuration_process_failed_temporary_unavailable">Erreur temporaire lors de la vérification de l\'éligibilité de votre appareil. Veuillez réessayer après un jour.</string>
<string name="personalid_configuration_process_failed_network_error">Erreur réseau lors de la vérification de l\'éligibilité de votre appareil. Veuillez vous assurer que vous disposez d\'une bonne connexion réseau et réessayez.</string>
<string name="personalid_configuration_process_failed_unexpected_error">Impossible de vérifier l\'éligibilité de l\'appareil en raison d\'une erreur inattendue. Veuillez contacter le support client si le problème persiste.</string>
<string name="personalid_photo_capture_title">Bienvenue %1$s</string>
<string name="personalid_photo_capture_subtitle">Veuillez prendre une photo de vous pour terminer la configuration de votre compte</string>
<string name="personalid_photo_capture_take_photo_button">Prendre une photo</string>
Expand Down
4 changes: 4 additions & 0 deletions app/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@
<string name="personalid_configuration_process_failed_title">Falha no processo</string>
<string name="personalid_configuration_process_failed_subtitle">De momento, o seu dispositivo não está qualificado para se inscrever no PersonalID. Tente novamente noutro dispositivo.</string>
<string name="personalid_configuration_process_failed_security_subtitle">De momento, o seu dispositivo não está qualificado para se inscrever no PersonalID devido à indisponibilidade da funcionalidade de segurança %s. Tente novamente noutro dispositivo.</string>
<string name="personalid_configuration_process_failed_play_services">Certifique-se de que a Play Store está disponível no seu dispositivo e está atualizada.</string>
<string name="personalid_configuration_process_failed_temporary_unavailable">Erro temporário ao verificar a elegibilidade do seu dispositivo. Por favor, tente novamente depois de um dia.</string>
<string name="personalid_configuration_process_failed_network_error">Erro de rede ao verificar a elegibilidade do seu dispositivo. Por favor, certifique-se de que tem uma boa conexão de rede e tente novamente.</string>
<string name="personalid_configuration_process_failed_unexpected_error">Não foi possível verificar a elegibilidade do dispositivo devido a um erro inesperado. Por favor, entre em contato com o suporte ao cliente se o problema persistir.</string>
<string name="personalid_photo_capture_title">Bem-vindo %1$s</string>
<string name="personalid_photo_capture_subtitle">Por favor, tire uma fotografia sua para concluir a configuração da sua conta</string>
<string name="personalid_photo_capture_take_photo_button">Tirar foto</string>
Expand Down
4 changes: 4 additions & 0 deletions app/res/values-sw/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@
<string name="personalid_configuration_process_failed_title">Mchakato Umeshindwa</string>
<string name="personalid_configuration_process_failed_subtitle">Kifaa chako hakijatimiza masharti ya kujisajili kwa Kitambulisho cha Kibinafsi kwa wakati huu. Tafadhali jaribu tena kwenye kifaa tofauti.</string>
<string name="personalid_configuration_process_failed_security_subtitle">Kifaa chako hakijatimiza masharti ya kujisajili kwa Kitambulisho cha Kibinafsi kwa wakati huu kwa sababu ya kutopatikana kwa kipengele cha usalama cha %s. Tafadhali jaribu tena kwenye kifaa tofauti.</string>
<string name="personalid_configuration_process_failed_play_services">Tafadhali hakikisha kuwa Play Store inapatikana kwenye kifaa chako na imesasishwa.</string>
<string name="personalid_configuration_process_failed_temporary_unavailable">Hitilafu ya muda katika kuthibitisha ustahiki wa kifaa chako. Tafadhali jaribu tena baada ya siku moja.</string>
<string name="personalid_configuration_process_failed_network_error">Hitilafu ya mtandao katika kuthibitisha ustahiki wa kifaa chako. Tafadhali hakikisha una muunganisho mzuri wa mtandao na jaribu tena.</string>
<string name="personalid_configuration_process_failed_unexpected_error">Imeshindikana kuthibitisha ustahiki wa kifaa kutokana na hitilafu isiyotarajiwa. Tafadhali wasiliana na huduma kwa wateja ikiwa tatizo litaendelea.</string>
<string name="personalid_photo_capture_title">Karibu %1$s</string>
<string name="personalid_photo_capture_subtitle">Tafadhali jipige picha ili ukamilishe kusanidi akaunti yako</string>
<string name="personalid_photo_capture_take_photo_button">Piga Picha</string>
Expand Down
4 changes: 4 additions & 0 deletions app/res/values-ti/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,10 @@
<string name="personalid_configuration_process_failed_title">መስርሕ ፈሺሉ።</string>
<string name="personalid_configuration_process_failed_subtitle">መሳርሒኻ ኣብዚ እዋን\'ዚ ን PersonalID ንምምዝጋብ ብቑዕ ኣይኮነን። በጃኹም ኣብ ካልእ መሳርሒ እንደገና ፈትኑ።</string>
<string name="personalid_configuration_process_failed_security_subtitle">መሳርሒኻ ኣብዚ እዋን እዚ ብሰንኪ %s ናይ ጸጥታ ባህሪ ዘይምህላዉ ንPersonalID ክምዝገብ ብቑዕ ኣይኮነን። በጃኹም ኣብ ካልእ መሳርሒ እንደገና ፈትኑ።</string>
<string name="personalid_configuration_process_failed_play_services">እባኮም ኣረጋግጹ ፡ እቲ ፕሌይ ስቶር ኣብ መሳርሒ እትርከቡ እንተሎ እና ትሓዘ እንተኾነ።</string>
<string name="personalid_configuration_process_failed_temporary_unavailable">ሓፋሽ ጸገብ ኣብ ምርግጋን ብትኽክል መሳርሒ እንተሃለወ። እባኮም ኣብ ሓደ መዓልቲ ደጊሞ ፈትኑ።</string>
<string name="personalid_configuration_process_failed_network_error">ጸገኒ መረብ ኣብ ምርግጋን ብትኽክል መሳርሒ እንተሃለወ። እባኮም ዝሓለፈ መርበብ ክርከቡ እንተኾነ ኣረጋግጹ እና ደጊሞ ፈትኑ።</string>
<string name="personalid_configuration_process_failed_unexpected_error">ብምኽንያት ዘይተገመተ ጸገብ ብትኽክል መሳርሒ ምርግጋን ኣይተሳነየን። ብቑጽሪ እቲ ጸገብ እንተተዓቐበ እባኮም ምምዕራያ ተሳትፎ ውሳነ ምግባር ድሕሪት ንደገና ተራኺቡ።</string>
<string name="personalid_photo_capture_title">እንቋዕ ብደሓን መጻእኩም %1$s</string>
<string name="personalid_photo_capture_subtitle">ምድላው ኣካውንትኩም ንምዝዛም ፎቶኹም ውሰዱ</string>
<string name="personalid_photo_capture_take_photo_button">ፎቶ ውሰድ</string>
Expand Down
4 changes: 4 additions & 0 deletions app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,10 @@
<string name="personalid_name_empty_error">Name field cannot be empty</string>
<string name="personalid_configuration_process_failed_title">Process Failed</string>
<string name="personalid_configuration_process_failed_subtitle">Your device isn\'t eligible to sign up for PersonalID at this time. Please try again on a different device.</string>
<string name="personalid_configuration_process_failed_play_services">Please make sure Play Store is available on your device and is updated.</string>
<string name="personalid_configuration_process_failed_temporary_unavailable">Temporary error in establishing your device eligibility. Please try again after a day.</string>
<string name="personalid_configuration_process_failed_network_error">Network error in establishing your device eligibility. Please make sure you have a good network connection and try again.</string>
<string name="personalid_configuration_process_failed_unexpected_error">Unable to establish device eligibility due to an unexpected error. Please contact customer support if the problem persists</string>
<string name="personalid_configuration_process_failed_security_subtitle">Your device isn\'t eligible to sign up for PersonalID at this time due to non availability of %s security feature. Please try again on a different device.</string>
<string name="personalid_photo_capture_title">Welcome %1$s</string>
<string name="personalid_photo_capture_subtitle">Please take a photo of yourself to complete your account setup</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.commcare.android.integrity

import android.content.Context
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import com.google.android.play.core.integrity.StandardIntegrityException
import com.google.android.play.core.integrity.model.StandardIntegrityErrorCode.*
import org.commcare.android.CommCareViewModelProvider
import org.commcare.utils.HashUtils
import kotlin.jvm.functions.Function2
import org.commcare.dalvik.R;
import org.json.JSONObject
import java.util.LinkedList
import java.util.HashMap
Expand All @@ -16,10 +19,11 @@ class IntegrityTokenApiRequestHelper(
lifecycleOwner: LifecycleOwner
) {
private val integrityTokenViewModel : IntegrityTokenViewModel = CommCareViewModelProvider.getIntegrityTokenViewModel()
private val pendingRequests = LinkedList<Pair<HashMap<String, String>, Function2<String?, String, Unit>>>()
private val pendingRequests = LinkedList<Pair<HashMap<String, String>, IntegrityTokenViewModel.IntegrityTokenCallback>>()

private var providerInitialized = false
private var providerFailed = false
private var providerFailedException = Exception("Integrity Token Provider failed to initialize")

init {
integrityTokenViewModel.providerState.observe(lifecycleOwner, object : Observer<IntegrityTokenViewModel.TokenProviderState> {
Expand All @@ -31,6 +35,7 @@ class IntegrityTokenApiRequestHelper(
}
is IntegrityTokenViewModel.TokenProviderState.Failure -> {
providerFailed = true
providerFailedException = state.exception
failPendingRequests()
}
}
Expand All @@ -40,19 +45,17 @@ class IntegrityTokenApiRequestHelper(

fun withIntegrityToken(
requestBody: HashMap<String, String>,
onTokenReady: Function2<String?, String, Unit>
callback: IntegrityTokenViewModel.IntegrityTokenCallback
) {
val jsonBody = JSONObject(requestBody as Map<*, *>).toString()
val requestHash = HashUtils.computeHash(jsonBody, HashUtils.HashAlgorithm.SHA256)

if (providerInitialized) {
integrityTokenViewModel.requestIntegrityToken(requestHash) { token ->
onTokenReady.invoke(token, requestHash)
}
integrityTokenViewModel.requestIntegrityToken(requestHash, false, callback)
} else if (providerFailed) {
onTokenReady.invoke(null, requestHash)
callback.onTokenFailure(providerFailedException)
} else {
pendingRequests.add(Pair(requestBody, onTokenReady))
pendingRequests.add(Pair(requestBody, callback))
}
}

Expand All @@ -61,18 +64,53 @@ class IntegrityTokenApiRequestHelper(
val (body, callback) = pendingRequests.removeFirst()
val jsonBody = JSONObject(body as Map<*, *>).toString()
val requestHash = HashUtils.computeHash(jsonBody, HashUtils.HashAlgorithm.SHA256)
integrityTokenViewModel.requestIntegrityToken(requestHash) { token ->
callback.invoke(token, requestHash)
}
integrityTokenViewModel.requestIntegrityToken(requestHash, false, callback)
}
}

private fun failPendingRequests() {
while (pendingRequests.isNotEmpty()) {
val (body, callback) = pendingRequests.removeFirst()
val jsonBody = JSONObject(body as Map<*, *>).toString()
val requestHash = HashUtils.computeHash(jsonBody, HashUtils.HashAlgorithm.SHA256)
callback.invoke(null, requestHash)
val (_, callback) = pendingRequests.removeFirst()
callback.onTokenFailure(providerFailedException)
}
}

fun getErrorForException(context: Context, exception: Exception): String {
var errorMessage = context.getString(R.string.personalid_configuration_process_failed_subtitle)
if (exception is StandardIntegrityException) {
val integrityErrorCode = exception.errorCode
when (integrityErrorCode) {
API_NOT_AVAILABLE,
PLAY_STORE_NOT_FOUND,
PLAY_SERVICES_NOT_FOUND,
PLAY_STORE_VERSION_OUTDATED,
PLAY_SERVICES_VERSION_OUTDATED,
CANNOT_BIND_TO_SERVICE -> {
errorMessage = context.getString(R.string.personalid_configuration_process_failed_play_services)
}

CLOUD_PROJECT_NUMBER_IS_INVALID,
REQUEST_HASH_TOO_LONG,
APP_UID_MISMATCH -> {
throw RuntimeException(exception)
}

TOO_MANY_REQUESTS,
GOOGLE_SERVER_UNAVAILABLE -> {
errorMessage = context.getString(R.string.personalid_configuration_process_failed_temporary_unavailable)
}

CLIENT_TRANSIENT_ERROR,
INTEGRITY_TOKEN_PROVIDER_INVALID -> {
errorMessage = context.getString(R.string.personalid_configuration_process_failed_unexpected_error)
}

NETWORK_ERROR -> {
errorMessage = context.getString(R.string.personalid_configuration_process_failed_network_error)
}
}
}
return errorMessage
}

}
60 changes: 52 additions & 8 deletions app/src/org/commcare/android/integrity/IntegrityTokenViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.google.android.play.core.integrity.IntegrityManagerFactory
import com.google.android.play.core.integrity.StandardIntegrityException
import com.google.android.play.core.integrity.StandardIntegrityManager.PrepareIntegrityTokenRequest
import com.google.android.play.core.integrity.StandardIntegrityManager.StandardIntegrityTokenProvider
import com.google.android.play.core.integrity.StandardIntegrityManager.StandardIntegrityTokenRequest
import org.commcare.CommCareApplication
import com.google.android.play.core.integrity.model.StandardIntegrityErrorCode
import org.commcare.dalvik.BuildConfig
import org.commcare.util.LogTypes
import org.javarosa.core.services.Logger

class IntegrityTokenViewModel(application: Application) : AndroidViewModel(application) {
Expand Down Expand Up @@ -42,36 +45,77 @@ class IntegrityTokenViewModel(application: Application) : AndroidViewModel(appli
).addOnSuccessListener { tokenProvider ->
integrityTokenProvider = tokenProvider
_providerState.postValue(TokenProviderState.Success(tokenProvider))
}.addOnFailureListener { exception -> _providerState.postValue(TokenProviderState.Failure(exception)) }
}.addOnFailureListener { exception ->
_providerState.postValue(TokenProviderState.Failure(exception))
Logger.exception("Error preparing Google Play Integrity token provider", exception)
}
}

/**
* Retrieves a Google Play Integrity token asynchronously.
*
* @param requestHash hash of the complete request we are planning to send the token with
* @param callback A callback function to handle the result
* @param hasRetried Indicates if this is a retry attempt after a failure
*/
fun requestIntegrityToken(requestHash: String, callback: IntegrityTokenCallback) {
fun requestIntegrityToken(requestHash: String, hasRetried: Boolean, callback: IntegrityTokenCallback) {
require(integrityTokenProvider != null) {"StandardIntegrityTokenProvider is not warmed up yet. Please try again"}
val integrityTokenResponse = integrityTokenProvider!!.request(
StandardIntegrityTokenRequest.builder()
.setRequestHash(requestHash)
.build()
)
integrityTokenResponse
.addOnSuccessListener { response -> callback.onTokenReceived(response.token()) }
.addOnSuccessListener { response -> callback.onTokenReceived(response.token(), requestHash) }
.addOnFailureListener { exception ->
Logger.exception("Error retrieving Google Play Integrity token", exception)
callback.onTokenReceived(null)
handleRequestFailureAndRetry(exception, requestHash, callback, hasRetried)
}
}

private fun handleRequestFailureAndRetry(
exception: java.lang.Exception,
requestHash: String,
callback: IntegrityTokenCallback,
hasRetried: Boolean
) {
if (exception is StandardIntegrityException && shouldRetryForIntegrityError(exception) && !hasRetried) {
Logger.log(LogTypes.TYPE_MAINTENANCE, "Integrity provider is invalid or outdated, re-preparing and retrying...")
prepareTokenProvider()

// Observe the new preparation and retry once
_providerState.observeForever(object : Observer<TokenProviderState> {
override fun onChanged(state: TokenProviderState) {
if (state is TokenProviderState.Success) {
_providerState.removeObserver(this)
requestIntegrityToken(requestHash, true, callback)
} else if (state is TokenProviderState.Failure) {
_providerState.removeObserver(this)
Logger.log("Error re-preparing token provider after failure", state.exception.message )
callback.onTokenFailure(state.exception)
}
}
})
} else {
callback.onTokenFailure(exception)
}
Logger.exception("Error retrieving Google Play Integrity token", exception)
}

private fun shouldRetryForIntegrityError(exception: StandardIntegrityException): Boolean {
return when (exception.errorCode) {
StandardIntegrityErrorCode.CLIENT_TRANSIENT_ERROR,
StandardIntegrityErrorCode.INTEGRITY_TOKEN_PROVIDER_INVALID -> true
else -> false
}
}

sealed class TokenProviderState {
data class Success(val provider: StandardIntegrityTokenProvider) : TokenProviderState()
data class Failure(val exception: Exception) : TokenProviderState()
}

fun interface IntegrityTokenCallback {
fun onTokenReceived(token: String?)
interface IntegrityTokenCallback {
fun onTokenReceived(token: String, requestHash: String)
fun onTokenFailure(exception: java.lang.Exception)
}
}
Loading