Skip to content

Commit

Permalink
chore: merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
JNdhlovu committed Sep 16, 2024
2 parents 32abd9e + 0551dc1 commit a4464eb
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 66 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
## 10.3.0
* Introduced inflow navigation as well as individual navigation for compose screens

## 10.2.7

* Fixed upload bug to retry in case a job already exists but zip was not uploaded
* Changed MLKit download modules to throw an exception explicitly if download fails

## 10.2.6

* Make document captured image confirmation to be optional
Expand Down
22 changes: 11 additions & 11 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
[versions]
accompanist-permissions = "0.34.0"
accompanist-permissions = "0.36.0"
android-gradle-plugin = "8.6.0"
androidx-activity = "1.9.1"
androidx-activity = "1.9.2"
androidx-annotation-experimental = "1.4.1"
androidx-compose-bom = "2024.08.00"
androidx-compose-bom = "2024.09.00"
androidx-core = "1.13.1"
androidx-core-splashscreen = "1.0.1"
androidx-fragment = "1.8.2"
androidx-lifecycle = "2.8.4"
androidx-navigation = "2.8.0-rc01"
androidx-fragment = "1.8.3"
androidx-lifecycle = "2.8.5"
androidx-navigation = "2.8.0"
androidx-test-core = "1.6.1"
androidx-test-espresso = "3.6.1"
androidx-test-fragment = "1.8.2"
androidx-test-fragment = "1.8.3"
androidx-test-junit = "1.2.1"
androidx-test-rules = "1.6.1"
camposer = "0.4.0"
camposer = "0.4.1"
chucker = "4.0.0"
coil = "2.7.0"
compose-lint-checks = "1.3.1"
coroutines = "1.8.1"
datastore = "1.1.1"
junit = "4.13.2"
kotlin = "2.0.20"
kotlin-immutable-collections = "0.3.7"
ksp = "2.0.20-1.0.24"
kotlin-immutable-collections = "0.3.8"
ksp = "2.0.20-1.0.25"
ktlint-plugin = "12.1.1"
leakcanary = "2.14"
lottie = "6.5.1"
lottie = "6.5.2"
maven-publish = "0.29.0"
mlkit-code-scanner = "16.1.0"
mlkit-obj-detection = "17.0.2"
Expand Down
2 changes: 1 addition & 1 deletion lib/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
10.2.5-SNAPSHOT
10.2.8-SNAPSHOT
111 changes: 77 additions & 34 deletions lib/src/main/java/com/smileidentity/SmileID.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE
import android.provider.Settings.Secure
import com.google.android.gms.common.moduleinstall.ModuleInstall
import com.google.android.gms.common.moduleinstall.ModuleInstallRequest
import com.google.android.gms.tasks.Tasks
import com.google.mlkit.common.sdkinternal.MlKitContext
import com.google.mlkit.vision.face.FaceDetection
import com.serjltt.moshi.adapters.FallbackEnum
Expand All @@ -15,7 +16,6 @@ import com.smileidentity.models.Config
import com.smileidentity.models.IdInfo
import com.smileidentity.models.JobType
import com.smileidentity.models.PrepUploadRequest
import com.smileidentity.models.SmileIDException
import com.smileidentity.models.UploadRequest
import com.smileidentity.networking.BiometricKycJobResultAdapter
import com.smileidentity.networking.DocumentVerificationJobResultAdapter
Expand Down Expand Up @@ -49,18 +49,23 @@ import com.smileidentity.util.getFilesByType
import com.smileidentity.util.getSmileTempFile
import com.smileidentity.util.handleOfflineJobFailure
import com.smileidentity.util.moveJobToSubmitted
import com.smileidentity.util.toSmileIDException
import com.squareup.moshi.Moshi
import io.sentry.Breadcrumb
import io.sentry.SentryLevel
import java.net.URL
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.HttpException
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
Expand Down Expand Up @@ -126,7 +131,6 @@ object SmileID {
if (enableCrashReporting) {
SmileIDCrashReporting.enable(isInDebugMode)
}
requestFaceDetectionModuleInstallation(context)

SmileID.useSandbox = useSandbox
val url = if (useSandbox) config.sandboxBaseUrl else config.prodBaseUrl
Expand All @@ -146,6 +150,14 @@ object SmileID {
fileSavePath = context.getDir("SmileID", MODE_PRIVATE).absolutePath
// ANDROID_ID may be null. Since Android 8, each app has a different value
Secure.getString(context.contentResolver, Secure.ANDROID_ID)?.let { fingerprint = it }

val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Timber.d("Face Detection Module Exception : ${throwable.message}")
throw throwable
}

val scope = CoroutineScope(Dispatchers.IO + SupervisorJob() + exceptionHandler)
scope.launch { requestFaceDetectionModuleInstallation(context) }
}

/**
Expand Down Expand Up @@ -326,17 +338,24 @@ object SmileID {
signature = authResponse.signature,
)

val prepUploadResponse = try {
val prepUploadResponse = runCatching {
api.prepUpload(prepUploadRequest)
} catch (e: SmileIDException) {
// It may be the case that Prep Upload was called during the job but the link expired.
// We need to pass retry=true in order to obtain a new link
if (e.details.code == "2215") {
api.prepUpload(prepUploadRequest.copy(retry = true))
} else {
throw e
}.recoverCatching { throwable ->
when {
throwable is HttpException -> {
val smileIDException = throwable.toSmileIDException()
if (smileIDException.details.code == "2215") {
api.prepUpload(prepUploadRequest.copy(retry = true))
} else {
throw smileIDException
}
}

else -> {
throw throwable
}
}
}
}.getOrThrow()

val selfieFileResult = getFileByType(jobId, FileType.SELFIE, submitted = false)
val livenessFilesResult = getFilesByType(jobId, FileType.LIVENESS, submitted = false)
Expand Down Expand Up @@ -478,29 +497,53 @@ object SmileID {
/**
* Request Google Play Services to install the Face Detection Module, if not already installed.
*/
private fun requestFaceDetectionModuleInstallation(context: Context) {
// see: https://github.com/googlesamples/mlkit/issues/264
MlKitContext.initializeIfNeeded(context)
val moduleInstallRequest = ModuleInstallRequest.newBuilder()
.addApi(FaceDetection.getClient())
.setListener {
val message = "Face Detection install status: " +
"errorCode=${it.errorCode}, " +
"installState=${it.installState}, " +
"bytesDownloaded=${it.progressInfo?.bytesDownloaded}, " +
"totalBytesToDownload=${it.progressInfo?.totalBytesToDownload}"
Timber.d(message)
SmileIDCrashReporting.hub.addBreadcrumb(message)
}.build()

ModuleInstall.getClient(context)
.installModules(moduleInstallRequest)
.addOnSuccessListener {
Timber.d("Face Detection install success: ${it.areModulesAlreadyInstalled()}")
}
.addOnFailureListener {
Timber.w(it, "Face Detection install failed")
SmileIDCrashReporting.hub.addBreadcrumb("Face Detection install failed")
@Suppress("ktlint:standard:max-line-length")
private suspend fun requestFaceDetectionModuleInstallation(context: Context) {
withContext(Dispatchers.IO) {
try {
// see: https://github.com/googlesamples/mlkit/issues/264
MlKitContext.initializeIfNeeded(context)

val moduleInstallRequest = ModuleInstallRequest.newBuilder()
.addApi(FaceDetection.getClient())
.setListener {
val message = "Face Detection install status: " +
"errorCode=${it.errorCode}, " +
"installState=${it.installState}, " +
"bytesDownloaded=${it.progressInfo?.bytesDownloaded}, " +
"totalBytesToDownload=${it.progressInfo?.totalBytesToDownload}"
Timber.d(message)
SmileIDCrashReporting.hub.addBreadcrumb(message)
}.build()

val installTask =
ModuleInstall.getClient(context).installModules(moduleInstallRequest)

Tasks.await(installTask)

if (installTask.isSuccessful) {
Timber.d(
"Face Detection install success: " +
"${installTask.result.areModulesAlreadyInstalled()}",
)
} else {
val exception = installTask.exception
?: Exception(
"An error occurred during Face Detection module installation." +
"See the guide here to use the bundled version " +
"https://developers.google.com/ml-kit/vision/face-detection/android",
)
Timber.w(exception, "Face Detection install failed")
SmileIDCrashReporting.hub.addBreadcrumb("Face Detection install failed")
throw exception
}
} catch (exception: Exception) {
Timber.e(exception, "Error during Face Detection module installation")
SmileIDCrashReporting.hub.addBreadcrumb(
"Error during Face Detection module installation: ${exception.message}",
)
throw exception
}
}
}
}
17 changes: 17 additions & 0 deletions lib/src/main/java/com/smileidentity/util/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,23 @@ fun getExceptionHandler(proxy: (Throwable) -> Unit) = CoroutineExceptionHandler
proxy(converted)
}

fun HttpException.toSmileIDException(): SmileIDException {
val errorBody = this.response()?.errorBody()?.string()
val adapter = moshi.adapter(SmileIDException.Details::class.java)

return try {
val details = adapter.fromJson(errorBody ?: "")
?: SmileIDException.Details(code = this.code().toString(), message = this.message())
SmileIDException(details)
} catch (e: Exception) {
Timber.w(e, "Unable to convert HttpException to SmileIDException")
// If parsing fails, create a SmileIDException with the HTTP status code and message
SmileIDException(
SmileIDException.Details(code = this.code().toString(), message = this.message()),
)
}
}

@Stable
sealed interface StringResource {
data class Text(val text: String) : StringResource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.smileidentity.util.getFilesByType
import com.smileidentity.util.handleOfflineJobFailure
import com.smileidentity.util.isNetworkFailure
import com.smileidentity.util.moveJobToSubmitted
import com.smileidentity.util.toSmileIDException
import io.sentry.Breadcrumb
import io.sentry.SentryLevel
import java.io.File
Expand All @@ -38,6 +39,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import retrofit2.HttpException
import timber.log.Timber

data class BiometricKycUiState(
Expand Down Expand Up @@ -145,17 +147,25 @@ class BiometricKycViewModel(
timestamp = authResponse.timestamp,
)

val prepUploadResponse = try {
val prepUploadResponse = runCatching {
SmileID.api.prepUpload(prepUploadRequest)
} catch (e: SmileIDException) {
// It may be the case that Prep Upload was called during the job but the link expired.
// We need to pass retry=true in order to obtain a new link
if (e.details.code == "2215") {
SmileID.api.prepUpload(prepUploadRequest.copy(retry = true))
} else {
throw e
}.recoverCatching { throwable ->
when {
throwable is HttpException -> {
val smileIDException = throwable.toSmileIDException()
if (smileIDException.details.code == "2215") {
SmileID.api.prepUpload(prepUploadRequest.copy(retry = true))
} else {
throw smileIDException
}
}

else -> {
throw throwable
}
}
}
}.getOrThrow()

val livenessImagesInfo = livenessFiles.map { it.asLivenessImage() }
val selfieImageInfo = selfieFile.asSelfieImage()
val uploadRequest = UploadRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.smileidentity.util.getFilesByType
import com.smileidentity.util.handleOfflineJobFailure
import com.smileidentity.util.isNetworkFailure
import com.smileidentity.util.moveJobToSubmitted
import com.smileidentity.util.toSmileIDException
import io.sentry.Breadcrumb
import io.sentry.SentryLevel
import java.io.File
Expand All @@ -45,6 +46,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import retrofit2.HttpException
import timber.log.Timber

internal data class OrchestratedDocumentUiState(
Expand Down Expand Up @@ -184,19 +186,25 @@ internal abstract class OrchestratedDocumentViewModel<T : Parcelable>(
timestamp = authResponse.timestamp,
)

val prepUploadResponse = try {
val prepUploadResponse = runCatching {
SmileID.api.prepUpload(prepUploadRequest)
} catch (e: SmileIDException) {
// It may be the case that Prep Upload was called during the job but the link expired.
// We need to pass retry=true in order to obtain a new link
if (e.details.code == "2215") {
SmileID.api.prepUpload(prepUploadRequest.copy(retry = true))
} else {
throw e
}.recoverCatching { throwable ->
when {
throwable is HttpException -> {
val smileIDException = throwable.toSmileIDException()
if (smileIDException.details.code == "2215") {
SmileID.api.prepUpload(prepUploadRequest.copy(retry = true))
} else {
throw smileIDException
}
}

else -> {
throw throwable
}
}
} catch (e: Exception) {
throw e
}
}.getOrThrow()

SmileID.api.upload(prepUploadResponse.uploadUrl, uploadRequest)
Timber.d("Upload finished")
sendResult(documentFrontFile, documentBackFile, livenessFiles)
Expand Down

0 comments on commit a4464eb

Please sign in to comment.