Skip to content
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

Feature/qr code detection update #102

Merged
merged 16 commits into from
Aug 26, 2021
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
25 changes: 25 additions & 0 deletions .idea/sonarIssues.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* ---license-start
* eu-digital-green-certificates / dgca-wallet-app-android
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*
* Created by osarapulov on 8/26/21 10:36 AM
*/

package dgca.wallet.app.android.certificate

import dgca.verifier.app.decoder.base45.Base45Service
import dgca.verifier.app.decoder.cbor.CborService
import dgca.verifier.app.decoder.compression.CompressorService
import dgca.verifier.app.decoder.cose.CoseService
import dgca.verifier.app.decoder.model.GreenCertificate
import dgca.verifier.app.decoder.model.VerificationResult
import dgca.verifier.app.decoder.prefixvalidation.PrefixValidationService
import dgca.verifier.app.decoder.schema.SchemaValidator
import timber.log.Timber

class DefaultGreenCertificateFetcher(
private val prefixValidationService: PrefixValidationService,
private val base45Service: Base45Service,
private val compressorService: CompressorService,
private val coseService: CoseService,
private val schemaValidator: SchemaValidator,
private val cborService: CborService,
) : GreenCertificateFetcher {
override fun fetchDataFromQrString(qrString: String): Pair<ByteArray?, GreenCertificate?> {
val verificationResult = VerificationResult()
val plainInput = prefixValidationService.decode(qrString, verificationResult)
val compressedCose = base45Service.decode(plainInput, verificationResult)
val coseResult: ByteArray? = compressorService.decode(compressedCose, verificationResult)

if (coseResult == null) {
Timber.d("Verification failed: Too many bytes read")
return Pair(null, null)
}
val cose: ByteArray = coseResult

val coseData = coseService.decode(cose, verificationResult)
if (coseData == null) {
Timber.d("Verification failed: COSE not decoded")
return Pair(cose, null)
}

schemaValidator.validate(coseData.cbor, verificationResult)
return Pair(cose, cborService.decode(coseData.cbor, verificationResult))
}

override fun fetchGreenCertificateFromQrString(qrString: String): GreenCertificate? = fetchDataFromQrString(qrString).second
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* ---license-start
* eu-digital-green-certificates / dgca-wallet-app-android
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*
* Created by osarapulov on 8/26/21 10:35 AM
*/

package dgca.wallet.app.android.certificate

import dgca.verifier.app.decoder.model.GreenCertificate

interface GreenCertificateFetcher {
fun fetchDataFromQrString(qrString: String): Pair<ByteArray?, GreenCertificate?>

fun fetchGreenCertificateFromQrString(qrString: String): GreenCertificate?
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ import android.net.Uri
interface BitmapFetcher {
fun loadBitmapByImageUri(uri: Uri): Bitmap

fun loadBitmapByPdfUri(uri: Uri): Bitmap
@Throws(Exception::class)
fun loadBitmapByPdfUri(uri: Uri): List<Bitmap>
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,19 @@ class DefaultBitmapFetcher(context: Context) : BitmapFetcher {
}.copy(Bitmap.Config.ARGB_8888, true)
}

override fun loadBitmapByPdfUri(uri: Uri): Bitmap =
@Throws(Exception::class)
override fun loadBitmapByPdfUri(uri: Uri): List<Bitmap> =
appContext.contentResolver.openFileDescriptor(uri, "r")!!.use { fileDescriptor ->
oleksandrsarapulovgl marked this conversation as resolved.
Show resolved Hide resolved
PdfRenderer(fileDescriptor).use { pdfRenderer ->
pdfRenderer.openPage(0).use { page ->
val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
bitmap
val bitmaps = mutableListOf<Bitmap>()
for (i in 0 until pdfRenderer.pageCount) {
pdfRenderer.openPage(i).use { page ->
val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
bitmaps.add(bitmap)
}
}
bitmaps.toList()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,22 @@

package dgca.wallet.app.android.certificate.add.pdf

import android.graphics.Bitmap
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dgca.verifier.app.decoder.model.GreenCertificate
import dgca.wallet.app.android.certificate.GreenCertificateFetcher
import dgca.wallet.app.android.certificate.add.BitmapFetcher
import dgca.wallet.app.android.certificate.add.FileSaver
import dgca.wallet.app.android.certificate.add.QrCodeFetcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject

sealed class ImportPdfResult {
Expand All @@ -46,7 +50,8 @@ sealed class ImportPdfResult {
class ImportPdfViewModel @Inject constructor(
private val bitmapFetcher: BitmapFetcher,
private val qrCodeFetcher: QrCodeFetcher,
private val fileSaver: FileSaver
private val fileSaver: FileSaver,
private val greenCertificateFetcher: GreenCertificateFetcher
) : ViewModel() {
private val _result = MutableLiveData<ImportPdfResult>()
val result: LiveData<ImportPdfResult> = _result
Expand All @@ -60,15 +65,34 @@ class ImportPdfViewModel @Inject constructor(
}

private fun Uri.handle(): ImportPdfResult {
val qrCodeString: String? = try {
bitmapFetcher.loadBitmapByPdfUri(this)
.let { bitmap -> qrCodeFetcher.fetchQrCodeString(bitmap) }
val qrStrings = mutableListOf<String>()
var bitmaps: List<Bitmap>? = null
try {
bitmaps = bitmapFetcher.loadBitmapByPdfUri(this)
bitmaps.forEach { bitmap ->
qrStrings.add(qrCodeFetcher.fetchQrCodeString(bitmap))
bitmap.recycle()
}
} catch (exception: Exception) {
null
Timber.d(exception, "Error fetching qr strings from bitmaps")
} finally {
bitmaps?.forEach { bitmap -> bitmap.recycle() }
}

var qrString = ""
var greenCertificate: GreenCertificate? = null

qrStrings.forEach { curQrString ->
val curGreenCertificate = greenCertificateFetcher.fetchGreenCertificateFromQrString(curQrString)
if (curGreenCertificate != null) {
qrString = curQrString
greenCertificate = curGreenCertificate
return@forEach
}
}

return if (qrCodeString?.isNotBlank() == true) {
ImportPdfResult.QrRecognised(qrCodeString)
return if (greenCertificate != null) {
ImportPdfResult.QrRecognised(qrString)
} else {
val file = try {
fileSaver.saveFileFromUri(this, "images", "${System.currentTimeMillis()}.pdf")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dgca.verifier.app.decoder.model.GreenCertificate
import dgca.wallet.app.android.certificate.GreenCertificateFetcher
import dgca.wallet.app.android.certificate.add.BitmapFetcher
import dgca.wallet.app.android.certificate.add.FileSaver
import dgca.wallet.app.android.certificate.add.QrCodeFetcher
Expand All @@ -46,7 +48,8 @@ sealed class PickImageResult {
class PickImageViewModel @Inject constructor(
private val qrCodeFetcher: QrCodeFetcher,
private val bitmapFetcher: BitmapFetcher,
private val fileSaver: FileSaver
private val fileSaver: FileSaver,
private val greenCertificateFetcher: GreenCertificateFetcher
) : ViewModel() {
private val _result = MutableLiveData<PickImageResult>()
val result: LiveData<PickImageResult> = _result
Expand All @@ -66,7 +69,10 @@ class PickImageViewModel @Inject constructor(
null
}

return if (qrCodeString?.isNotBlank() == true) {
val greenCertificate: GreenCertificate? =
qrCodeString?.let { qrString -> greenCertificateFetcher.fetchGreenCertificateFromQrString(qrString) }

return if (greenCertificate != null) {
PickImageResult.QrRecognised(qrCodeString)
} else {
val file = try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dgca.verifier.app.decoder.model.GreenCertificate
import dgca.wallet.app.android.certificate.GreenCertificateFetcher
import dgca.wallet.app.android.certificate.add.BitmapFetcher
import dgca.wallet.app.android.certificate.add.FileSaver
import dgca.wallet.app.android.certificate.add.QrCodeFetcher
Expand All @@ -48,7 +50,8 @@ class TakePhotoViewModel @Inject constructor(
private val qrCodeFetcher: QrCodeFetcher,
private val bitmapFetcher: BitmapFetcher,
private val uriProvider: UriProvider,
private val fileSaver: FileSaver
private val fileSaver: FileSaver,
private val greenCertificateFetcher: GreenCertificateFetcher
) : ViewModel() {
val uriLiveData: LiveData<Uri> = MutableLiveData(uriProvider.getUriFor("temp", "temp.jpeg"))
private val _result = MutableLiveData<TakePhotoResult>()
Expand All @@ -70,9 +73,13 @@ class TakePhotoViewModel @Inject constructor(
} catch (exception: Exception) {
null
}
val greenCertificate: GreenCertificate? =
qrCodeString?.let { qrString -> greenCertificateFetcher.fetchGreenCertificateFromQrString(qrString) }

return when {
qrCodeString?.isNotBlank() == true && uriProvider.deleteFileByUri(this) -> {
greenCertificate != null && uriProvider.deleteFileByUri(
this
) -> {
TakePhotoResult.QrRecognised(qrCodeString)
}
else -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dgca.verifier.app.decoder.*
import dgca.verifier.app.decoder.base45.Base45Service
import dgca.verifier.app.decoder.cbor.CborService
import dgca.verifier.app.decoder.compression.CompressorService
import dgca.verifier.app.decoder.cose.CoseService
import dgca.verifier.app.decoder.model.GreenCertificate
import dgca.verifier.app.decoder.model.VerificationResult
import dgca.verifier.app.decoder.prefixvalidation.PrefixValidationService
import dgca.verifier.app.decoder.schema.SchemaValidator
import dgca.wallet.app.android.BuildConfig
import dgca.wallet.app.android.Event
import dgca.wallet.app.android.certificate.GreenCertificateFetcher
import dgca.wallet.app.android.data.CertificateModel
import dgca.wallet.app.android.data.ConfigRepository
import dgca.wallet.app.android.data.WalletRepository
Expand All @@ -56,12 +51,8 @@ import javax.inject.Inject

@HiltViewModel
class ClaimCertificateViewModel @Inject constructor(
private val greenCertificateFetcher: GreenCertificateFetcher,
private val prefixValidationService: PrefixValidationService,
private val base45Service: Base45Service,
private val compressorService: CompressorService,
private val coseService: CoseService,
private val schemaValidator: SchemaValidator,
private val cborService: CborService,
private val configRepository: ConfigRepository,
private val walletRepository: WalletRepository
) : ViewModel() {
Expand All @@ -81,26 +72,9 @@ class ClaimCertificateViewModel @Inject constructor(
fun init(qrCodeText: String) {
viewModelScope.launch {
_inProgress.value = true
withContext(Dispatchers.IO) {
val verificationResult = VerificationResult()
val plainInput = prefixValidationService.decode(qrCodeText, verificationResult)
val compressedCose = base45Service.decode(plainInput, verificationResult)
val coseResult: ByteArray? = compressorService.decode(compressedCose, verificationResult)

if (coseResult == null) {
Timber.d("Verification failed: Too many bytes read")
return@withContext
}
cose = coseResult

val coseData = coseService.decode(cose, verificationResult)
if (coseData == null) {
Timber.d("Verification failed: COSE not decoded")
return@withContext
}

schemaValidator.validate(coseData.cbor, verificationResult)
greenCertificate = cborService.decode(coseData.cbor, verificationResult)
withContext(Dispatchers.IO) { greenCertificateFetcher.fetchDataFromQrString(qrCodeText) }.apply {
cose = first ?: cose
greenCertificate = second
}
_inProgress.value = false
_certificate.value = greenCertificate?.toCertificateModel()
Expand Down
Loading