Skip to content
Snippets Groups Projects
Unverified Commit fc844e3a authored by Mohamed Metwalli's avatar Mohamed Metwalli Committed by GitHub
Browse files

Fix Presence tracing Crash (EXPOSUREAPP-7751) (#3404)


* Handle validation errors from deep-links

* Add unit tests

Co-authored-by: default avatarMatthias Urhahn <matthias.urhahn@sap.com>
parent c20fa348
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins
import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
import de.rki.coronawarnapp.presencetracing.checkins.qrcode.VerifiedTraceLocation
import de.rki.coronawarnapp.util.ui.LazyString
sealed class CheckInEvent {
......@@ -17,6 +18,8 @@ sealed class CheckInEvent {
data class ConfirmSwipeItem(val checkIn: CheckIn, val position: Int) : CheckInEvent()
data class InvalidQrCode(val errorText: LazyString) : CheckInEvent()
object ShowInformation : CheckInEvent()
object OpenDeviceSettings : CheckInEvent()
......
......@@ -31,6 +31,7 @@ import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator
import de.rki.coronawarnapp.util.lists.diffutil.update
import de.rki.coronawarnapp.util.onScroll
import de.rki.coronawarnapp.util.tryHumanReadableError
import de.rki.coronawarnapp.util.ui.LazyString
import de.rki.coronawarnapp.util.ui.doNavigate
import de.rki.coronawarnapp.util.ui.observe2
import de.rki.coronawarnapp.util.ui.viewBinding
......@@ -66,14 +67,8 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag
bindRecycler()
bindFAB()
viewModel.checkins.observe2(this) { items ->
updateViews(items)
}
viewModel.events.observe2(this) {
onNavigationEvent(it)
}
viewModel.checkins.observe2(this) { items -> updateViews(items) }
viewModel.events.observe2(this) { it?.let { onNavigationEvent(it) } }
viewModel.errorEvent.observe2(this) {
val errorForHumans = it.tryHumanReadableError(requireContext())
Toast.makeText(requireContext(), errorForHumans.description, Toast.LENGTH_LONG).show()
......@@ -85,7 +80,7 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag
viewModel.checkCameraSettings()
}
private fun onNavigationEvent(event: CheckInEvent?) {
private fun onNavigationEvent(event: CheckInEvent) {
when (event) {
is CheckInEvent.ConfirmCheckIn -> {
setupAxisTransition()
......@@ -127,9 +122,21 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag
doNavigate(CheckInsFragmentDirections.actionCheckInsFragmentToCheckInOnboardingFragment(false))
}
is CheckInEvent.OpenDeviceSettings -> openDeviceSettings()
is CheckInEvent.InvalidQrCode -> showInvalidQrCodeInformation(event.errorText)
}
}
private fun showInvalidQrCodeInformation(lazyErrorText: LazyString) {
val errorText = lazyErrorText.get(requireContext())
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.trace_location_attendee_invalid_qr_code_dialog_title)
.setMessage(getString(R.string.trace_location_attendee_invalid_qr_code_dialog_message, errorText))
.setPositiveButton(R.string.trace_location_attendee_invalid_qr_code_dialog_positive_button) { _, _ ->
// NO-OP
}
.show()
}
private fun updateViews(items: List<CheckInsItem>) {
checkInsAdapter.update(items)
binding.apply {
......
......@@ -20,6 +20,8 @@ import de.rki.coronawarnapp.util.coroutine.AppScope
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.flow.intervalFlow
import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.ui.toLazyString
import de.rki.coronawarnapp.util.ui.toResolvingString
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
import kotlinx.coroutines.CoroutineScope
......@@ -140,15 +142,24 @@ class CheckInsViewModel @AssistedInject constructor(
}
private fun verifyUri(uri: String) = launch {
Timber.i("uri: $uri")
val qrCodePayload = qrCodeUriParser.getQrCodePayload(uri)
when (val verifyResult = traceLocationVerifier.verifyTraceLocation(qrCodePayload)) {
is TraceLocationVerifier.VerificationResult.Valid -> events.postValue(
if (cleanHistory)
CheckInEvent.ConfirmCheckInWithoutHistory(verifyResult.verifiedTraceLocation)
else
CheckInEvent.ConfirmCheckIn(verifyResult.verifiedTraceLocation)
)
try {
Timber.i("uri: $uri")
val qrCodePayload = qrCodeUriParser.getQrCodePayload(uri)
when (val verifyResult = traceLocationVerifier.verifyTraceLocation(qrCodePayload)) {
is TraceLocationVerifier.VerificationResult.Valid -> events.postValue(
if (cleanHistory)
CheckInEvent.ConfirmCheckInWithoutHistory(verifyResult.verifiedTraceLocation)
else
CheckInEvent.ConfirmCheckIn(verifyResult.verifiedTraceLocation)
)
is TraceLocationVerifier.VerificationResult.Invalid -> events.postValue(
CheckInEvent.InvalidQrCode(verifyResult.errorTextRes.toResolvingString())
)
}
} catch (e: Exception) {
Timber.d(e, "TraceLocation verification failed")
val msg = e.message ?: "QR-Code was invalid"
events.postValue(CheckInEvent.InvalidQrCode(msg.toLazyString()))
}
}
......
......@@ -6,11 +6,14 @@ import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
import de.rki.coronawarnapp.presencetracing.checkins.qrcode.QRCodeUriParser
import de.rki.coronawarnapp.presencetracing.checkins.qrcode.TraceLocationVerifier
import de.rki.coronawarnapp.presencetracing.checkins.checkout.CheckOutHandler
import de.rki.coronawarnapp.presencetracing.checkins.qrcode.InvalidQrCodeDataException
import de.rki.coronawarnapp.presencetracing.checkins.qrcode.InvalidQrCodeUriException
import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.ActiveCheckInVH
import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.CameraPermissionVH
import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.PastCheckInVH
import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.permission.CameraPermissionProvider
import io.kotest.assertions.throwables.shouldNotThrow
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeInstanceOf
import io.mockk.MockKAnnotations
......@@ -180,6 +183,32 @@ class CheckInsViewModelTest : BaseTest() {
}
}
@Test
fun `Handle uri InvalidQrCodeUriException`() = runBlockingTest {
every { savedState.get<String>("deeplink.last") } returns null
coEvery { qrCodeUriParser.getQrCodePayload(any()) } throws InvalidQrCodeUriException("Invalid")
val url = "https://e.coronawarn.app?v=1#place_holder"
shouldNotThrow<InvalidQrCodeUriException> {
createInstance(deepLink = url, scope = this).apply {
events.getOrAwaitValue().shouldBeInstanceOf<CheckInEvent.InvalidQrCode>()
}
}
}
@Test
fun `Handle uri InvalidQrCodeDataException`() = runBlockingTest {
every { savedState.get<String>("deeplink.last") } returns null
coEvery { qrCodeUriParser.getQrCodePayload(any()) } throws InvalidQrCodeDataException("Invalid")
val url = "https://e.coronawarn.app?v=1#place_holder"
shouldNotThrow<InvalidQrCodeDataException> {
createInstance(deepLink = url, scope = this).apply {
events.getOrAwaitValue().shouldBeInstanceOf<CheckInEvent.InvalidQrCode>()
}
}
}
private fun createInstance(deepLink: String?, scope: CoroutineScope) =
CheckInsViewModel(
savedState = savedState,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment