From 34aaaee33ac71189df1842f6a9e1a9fcb569735d Mon Sep 17 00:00:00 2001 From: Jan Kobersky <5406945+kober32@users.noreply.github.com> Date: Mon, 4 Mar 2024 23:26:26 +0100 Subject: [PATCH] Added inline logging (#7) --- .../digitalonboarding/ActivationService.kt | 49 +++++++++++++--- .../android/digitalonboarding/Logger.kt | 5 ++ .../digitalonboarding/VerificationService.kt | 58 ++++++++++++++++++- 3 files changed, 102 insertions(+), 10 deletions(-) diff --git a/library/src/main/java/com/wultra/android/digitalonboarding/ActivationService.kt b/library/src/main/java/com/wultra/android/digitalonboarding/ActivationService.kt index 2f19368..d4ef70c 100644 --- a/library/src/main/java/com/wultra/android/digitalonboarding/ActivationService.kt +++ b/library/src/main/java/com/wultra/android/digitalonboarding/ActivationService.kt @@ -105,6 +105,7 @@ class ActivationService( init { if (!canRestoreSession) { + D.warning("Created ActivationService without restoration.") processId = null } } @@ -126,6 +127,8 @@ class ActivationService( */ fun status(callback: (Result) -> Unit) { + D.print("Retrieving the status") + val processId = guardProcessId(callback) ?: return if (!verifyCanStartProcess(callback)) return @@ -134,11 +137,16 @@ class ActivationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: GetStatusResponse) { - callback(Result.success(Status.fromResponse(result))) + val success = Status.fromResponse(result) + D.print("Status call success: ${success.name}") + callback(Result.success(success)) } override fun onFailure(error: ApiError) { - callback(Result.failure(error.toException())) + error.toException().also { + D.error(it) + callback(Result.failure(it)) + } } } ) @@ -154,6 +162,7 @@ class ActivationService( fun start(credentials: T, callback: (Result) -> Unit) { if (processId != null) { + D.error("Activation can be started only when another activation is not in progress.") callback(Result.failure(ActivationInProgressException)) return } @@ -165,11 +174,15 @@ class ActivationService( object : IApiCallResponseListener { override fun onSuccess(result: StartOnboardingResponse) { processId = result.responseObject.processId + D.print("Start successful") callback(Result.success(Unit)) } override fun onFailure(error: ApiError) { - callback(Result.failure(error.toException())) + error.toException().also { + D.error(it) + callback(Result.failure(it)) + } } } ) @@ -192,15 +205,20 @@ class ActivationService( object : IApiCallResponseListener { override fun onSuccess(result: StatusResponse) { this@ActivationService.processId = null + D.print("Cancel successful") callback(Result.success(Unit)) } override fun onFailure(error: ApiError) { if (forceCancel) { this@ActivationService.processId = null + D.warning("Cancel failed, but forceCancel was used - returning success anyway.") callback(Result.success(Unit)) } else { - callback(Result.failure(error.toException())) + error.toException().also { + D.error(it) + callback(Result.failure(it)) + } } } } @@ -210,6 +228,7 @@ class ActivationService( /** Clears the stored data (without networking call). */ fun clear() { processId = null + D.print("Clear successful") } /** @@ -227,18 +246,26 @@ class ActivationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: StatusResponse) { + D.print("Clear successful") callback(Result.success(Unit)) } override fun onFailure(error: ApiError) { - callback(Result.failure(error.toException())) + error.toException().also { + D.error(it) + callback(Result.failure(it)) + } } } ) } fun createPowerAuthActivationData(otp: String): ActivationData? { - val processId = processId ?: return null + val processId = processId + if (processId == null) { + D.error("Cannot create activation data - process not started (missing processId).") + return null + } return ActivationDataWithOTP(processId, otp) } @@ -263,6 +290,7 @@ class ActivationService( powerAuthSDK.createActivation(data, activationName) { result -> result.onSuccess { this.processId = null + D.print("PowerAuth activation created") callback(Result.success(it)) }.onFailure { // when no longer possible to retry activation @@ -270,6 +298,7 @@ class ActivationService( if ((it as? FailedApiException)?.allowOnboardingOtpRetry() == false) { this.processId = null } + D.print("PowerAuth activation failed - $it") callback(Result.failure(it)) } } @@ -279,6 +308,7 @@ class ActivationService( val processId = this.processId if (processId == null) { + D.error("ProcessId is required for the requested method but not available. This mean that the process was not started.") callback(Result.failure(ActivationNotRunningException)) return null } @@ -288,6 +318,7 @@ class ActivationService( private fun verifyCanStartProcess(callback: (Result) -> Unit): Boolean { if (!powerAuthSDK.canStartActivation()) { + D.error("Cannot start the activation: PowerAuthSDK.canStartActivation() == false") processId = null callback(Result.failure(CannotActivateException)) return false @@ -308,11 +339,15 @@ class ActivationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: OTPDetailResponse) { + D.print("Get OTP successful") callback(Result.success(result.responseObject.otpCode)) } override fun onFailure(error: ApiError) { - callback(Result.failure(error.toException())) + error.toException().also { + D.error(it) + callback(Result.failure(it)) + } } } ) diff --git a/library/src/main/java/com/wultra/android/digitalonboarding/Logger.kt b/library/src/main/java/com/wultra/android/digitalonboarding/Logger.kt index b5ee0d0..942ae94 100644 --- a/library/src/main/java/com/wultra/android/digitalonboarding/Logger.kt +++ b/library/src/main/java/com/wultra/android/digitalonboarding/Logger.kt @@ -12,6 +12,7 @@ package com.wultra.android.digitalonboarding import android.util.Log +import com.wultra.android.powerauth.networking.error.ApiError class Logger { @@ -64,6 +65,10 @@ class Logger { } } + internal fun error(error: ApiError) { + error(error.toException()) + } + internal fun error(throwable: Throwable) { error(throwable.message ?: throwable.toString()) } diff --git a/library/src/main/java/com/wultra/android/digitalonboarding/VerificationService.kt b/library/src/main/java/com/wultra/android/digitalonboarding/VerificationService.kt index fa6d944..666673b 100644 --- a/library/src/main/java/com/wultra/android/digitalonboarding/VerificationService.kt +++ b/library/src/main/java/com/wultra/android/digitalonboarding/VerificationService.kt @@ -90,13 +90,18 @@ class VerificationService( init { if (!powerAuthSDK.hasValidActivation()) { + D.print("PowerAuth has not a valid activation - clearing cache.") cachedProcess = null } } /** Time in seconds that user needs to wait between OTP resend calls */ fun otpResendPeriodInSeconds(): Long? { - val period = lastStatus?.responseObject?.config?.otpResendPeriod ?: return null + val period = lastStatus?.responseObject?.config?.otpResendPeriod + if (period == null) { + D.warning("OTP resend period can be provided only when there was at least 1 status call made") + return null + } return try { Duration.parse(period).seconds } catch (t: Throwable) { @@ -114,6 +119,7 @@ class VerificationService( override fun onSuccess(result: VerificationStatusResponse) { when (result.responseObject.status) { IdentityVerificationStatus.FAILED, IdentityVerificationStatus.REJECTED, IdentityVerificationStatus.NOT_INITIALIZED, IdentityVerificationStatus.ACCEPTED -> { + D.print("We reached endstate, clearing cache") cachedProcess = null } else -> { @@ -124,44 +130,59 @@ class VerificationService( lastStatus = result val nextStep = VerificationStatusNextStep.fromStatusResponse(result.responseObject) + + D.print("Response status: ${result.responseObject.status.name}:${result.responseObject.phase?.name}. NextStep: ${nextStep.value.name}") + when (nextStep.value) { Value.INTRO -> { markCompleted(VerificationStateIntroData, callback) } Value.DOCUMENT_SCAN -> { + D.print("Asking for a document status.") api.documentsStatus( result.responseObject.processId, object : IApiCallResponseListener { override fun onSuccess(result: DocumentsStatusResponse) { - val documents = result.responseObject.documents + D.print("Document status success.") + + val documents = result.responseObject.documents val cachedProcess = this@VerificationService.cachedProcess if (cachedProcess != null) { + D.print("Cached process obtained, processing retrieved documents") cachedProcess.feed(documents) if (documents.any { it.action() == DocumentAction.ERROR } || documents.any { !it.errors.isNullOrEmpty() }) { + D.print("There is an document error - returning.") markCompleted(VerificationStateScanDocumentData(cachedProcess), callback) } else if (documents.all { it.action() == DocumentAction.PROCEED }) { + D.print("Document is waiting - continue scanning.") markCompleted(VerificationStateScanDocumentData(cachedProcess), callback) } else if (documents.any { it.action() == DocumentAction.WAIT }) { // TODO: really verification? + D.print("Document is processing - wait..") markCompleted(VerificationStateProcessingData(ProcessingItem.DOCUMENT_VERIFICATION), callback) } else if (documents.isEmpty()) { + D.print("There are no document - scan first.") markCompleted(VerificationStateScanDocumentData(cachedProcess), callback) } else { // TODO: is this ok? + D.warning("Unexpected document state - configuration error.") markCompleted(VerificationStateFailedData, callback) } } else { if (documents.isEmpty()) { + D.print("No documents scanned - start scanning") markCompleted(VerificationStateDocumentsToScanSelectData, callback) } else { + D.warning("Unexpected document state - configuration/cache error.") markCompleted(VerificationStateFailedData, callback) } } } override fun onFailure(error: ApiError) { + D.error("Document status failed : ${error.toException()}") markCompleted(error, callback) } } @@ -189,6 +210,7 @@ class VerificationService( } override fun onFailure(error: ApiError) { + D.error("Status failed : ${error.toException()}") lastStatus = null markCompleted(error, callback) } @@ -208,6 +230,7 @@ class VerificationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: ConsentResponse) { + D.print("consentGet success") markCompleted( VerificationStateConsentData(result.responseObject.consentText), callback, @@ -215,6 +238,7 @@ class VerificationService( } override fun onFailure(error: ApiError) { + D.error("consentGet failed : ${error.toException()}") markCompleted(error, callback) } }, @@ -235,14 +259,17 @@ class VerificationService( true, object : IApiCallResponseListener { override fun onSuccess(result: ConsentApproveResponse) { + D.print("consentApprove success - starting the process") api.start( processId, object : IApiCallResponseListener { override fun onSuccess(result: StatusResponse) { + D.print("start success") markCompleted(VerificationStateDocumentsToScanSelectData, callback) } override fun onFailure(error: ApiError) { + D.error("start failed : ${error.toException()}") markCompleted(error, callback) } }, @@ -250,6 +277,7 @@ class VerificationService( } override fun onFailure(error: ApiError) { + D.error("consentApprove failed : ${error.toException()}") markCompleted(error, callback) } }, @@ -271,10 +299,12 @@ class VerificationService( challenge, object : IApiCallResponseListener { override fun onSuccess(result: SDKInitResponse) { + D.print("documentsInitSDK success with token: ${result.responseObject.attributes.responseToken}") markCompleted(result.responseObject.attributes.responseToken, callback) } override fun onFailure(error: ApiError) { + D.error("consentApprove failed : ${error.toException()}") markCompleted(error, callback) } }, @@ -291,6 +321,7 @@ class VerificationService( // TODO: We should verify that we're in the expected state here val process = VerificationScanProcess(types) cachedProcess = process + D.print("Settings documents to scan: ${types.joinToString(",") { it.name }}") markCompleted(VerificationStateScanDocumentData(process), callback) } @@ -313,6 +344,7 @@ class VerificationService( requestData, object : IApiCallResponseListener { override fun onSuccess(result: DocumentSubmitResponse) { + D.print("document submitted") markCompleted( VerificationStateProcessingData(ProcessingItem.DOCUMENT_UPLOAD), callback @@ -320,11 +352,13 @@ class VerificationService( } override fun onFailure(error: ApiError) { + D.error("documentsSubmit failed : ${error.toException()}") markCompleted(error, callback) } } ) } catch (e: Throwable) { + D.error("Failed to create payload data : $e") markCompleted(ApiError(e), callback) } } @@ -342,10 +376,12 @@ class VerificationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: PresenceCheckResponse) { + D.print("presenceCheckInit success with: ${result.responseObject.attributes}") markCompleted(result.responseObject.attributes, callback) } override fun onFailure(error: ApiError) { + D.error("presenceCheckInit failed : ${error.toException()}") markCompleted(error, callback) } } @@ -365,6 +401,7 @@ class VerificationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: StatusResponse) { + D.print("presenceCheckSubmit success") markCompleted( VerificationStateProcessingData(ProcessingItem.VERIFYING_PRESENCE), callback @@ -372,6 +409,7 @@ class VerificationService( } override fun onFailure(error: ApiError) { + D.error("presenceCheckSubmit failed : ${error.toException()}") markCompleted(error, callback) } } @@ -391,10 +429,12 @@ class VerificationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: StatusResponse) { + D.print("restartVerification success") markCompleted(VerificationStateIntroData, callback) } override fun onFailure(error: ApiError) { + D.error("restartVerification failed : ${error.toException()}") markCompleted(error, callback) } } @@ -414,10 +454,12 @@ class VerificationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: StatusResponse) { + D.print("cancelWholeProcess success") callback(Result.success(Unit)) } override fun onFailure(error: ApiError) { + D.error("cancelWholeProcess failed : ${error.toException()}") callback(Result.failure(error.toException())) } } @@ -440,11 +482,13 @@ class VerificationService( object : IApiCallResponseListener { override fun onSuccess(result: VerifyOtpResponse) { if (result.responseObject.verified) { + D.print("verifyOTP success") markCompleted( VerificationStateProcessingData(ProcessingItem.OTHER), callback, ) } else { + D.error("verifyOTP failed. remainingAttempts: ${result.responseObject.remainingAttempts}") if (result.responseObject.remainingAttempts > 0 && !result.responseObject.expired) { markCompleted( VerificationStateOtpData(result.responseObject.remainingAttempts), @@ -458,6 +502,7 @@ class VerificationService( } override fun onFailure(error: ApiError) { + D.error("verifyOTP failed : ${error.toException()}") markCompleted(error, callback) } }, @@ -477,10 +522,12 @@ class VerificationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: ResendOtpResponse) { + D.print("resendOTP success") callback(Result.success(Unit)) } override fun onFailure(error: ApiError) { + D.error("verifyOTP failed : ${error.toException()}") markCompleted(error, callback) } }, @@ -500,10 +547,12 @@ class VerificationService( processId, object : IApiCallResponseListener { override fun onSuccess(result: OTPDetailResponse) { + D.print("getOTP success") callback(Result.success(result.responseObject.otpCode)) } override fun onFailure(error: ApiError) { + D.error("verifyOTP failed : ${error.toException()}") callback(Result.failure(Fail(error))) } } @@ -549,6 +598,7 @@ class VerificationService( val processId = lastStatus?.responseObject?.processId if (processId == null) { + D.error("ProcessId is required for the requested method but not available. This mean that the process was not started or status was not fetched yet.") markCompleted(Fail(ApiError(ActivationMissingStatusException)), callback) return null } @@ -557,12 +607,13 @@ class VerificationService( private fun markCompleted(error: ApiError, callback: (Result) -> Unit) { if (!error.isOffline() || error.error == ApiErrorCode.POWERAUTH_AUTH_FAIL) { - + D.error("Fetching activation status to determine if activation was not removed on the server") powerAuthSDK.fetchActivationStatusWithCallback( appContext, object : IActivationStatusListener { override fun onActivationStatusSucceed(status: ActivationStatus?) { if (status?.state != ActivationStatus.State_Active) { + D.error("PowerAuth status not active.") listener?.powerAuthActivationStatusChanged(this@VerificationService, status) markCompleted(Fail(ApiError(ActivationNotActiveException)), callback) } else { @@ -571,6 +622,7 @@ class VerificationService( } override fun onActivationStatusFailed(t: Throwable) { + D.error("Failed to retrieve PowerAuth status") markCompleted(Fail(ApiError(t)), callback) } }