Skip to content

Commit

Permalink
Feature/qr code detection update (#102)
Browse files Browse the repository at this point in the history
* Added scanning QR code
* Added scanning of QRs fetched from taking photo
* Extracted logic to fetch green certificate
* Extracted request keys for adding qrs
* Updated PDF scanning logic
* Fixed PDF scanning logic
  • Loading branch information
oleksandrsarapulovgl authored Aug 26, 2021
1 parent 89002fd commit 08a878d
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 48 deletions.
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 ->
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

0 comments on commit 08a878d

Please sign in to comment.