Skip to content
This repository was archived by the owner on Jun 20, 2023. It is now read-only.

Fault Tolerance - BNR triggered again because of TC (EXPOSUREAPP-14218) #5662

Merged
merged 12 commits into from
Oct 27, 2022
6 changes: 3 additions & 3 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ jobs:
path: Corona-Warn-App/build/test-results

- name: Publish Test Results - Device Release
uses: dorny/test-reporter@v1.5.0
uses: dorny/test-reporter@v1.6.0
if: always()
with:
name: "Unit Test Results - Device Release"
Expand Down Expand Up @@ -218,7 +218,7 @@ jobs:
path: Corona-Warn-App/build/test-results

- name: Publish Test Results - Device For Tester Release
uses: dorny/test-reporter@v1.5.0
uses: dorny/test-reporter@v1.6.0
if: always()
with:
name: "Unit Test Results - Device For Tester Release"
Expand Down Expand Up @@ -343,7 +343,7 @@ jobs:
path: firebase-results

- name: Publish Firebase Test Results
uses: dorny/test-reporter@v1.5.0
uses: dorny/test-reporter@v1.6.0
if: always()
with:
name: "Firebase Test Results"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
sudo gsutil -m cp -R -U gs://${GOOGLE_PROJECT_ID}-circleci-android/${{ env.BUCKETDIR }}/blueline* mkdir firebase-screenshots

- name: Publish Firebase Test Results
uses: dorny/test-reporter@v1.5.0
uses: dorny/test-reporter@v1.6.0
if: always()
with:
name: "Firebase Test Results"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class BoosterInfoDetailsFragmentTest : BaseUITest() {
setupMockViewModel(
object : BoosterInfoDetailsViewModel.Factory {
override fun create(
personIdentifierCode: String
groupKey: String
): BoosterInfoDetailsViewModel = viewModel
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class PersonDetailsFragmentTest : BaseUITest() {
setupMockViewModel(
object : PersonDetailsViewModel.Factory {
override fun create(
personIdentifierCode: String,
groupKey: String,
colorShade: PersonColorShade
): PersonDetailsViewModel = viewModel
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class DccReissuanceConsentFragmentTest : BaseUITest() {
}
}

private val args = DccReissuanceConsentFragmentArgs(personIdentifierCode = "personIdentifierCode").toBundle()
private val args = DccReissuanceConsentFragmentArgs(groupKey = "personIdentifierCode").toBundle()

private val state = DccReissuanceConsentViewModel.State(
certificateList = mutableListOf<DccReissuanceItem>(
Expand Down Expand Up @@ -102,7 +102,7 @@ class DccReissuanceConsentFragmentTest : BaseUITest() {

setupMockViewModel(
factory = object : DccReissuanceConsentViewModel.Factory {
override fun create(personIdentifierCode: String): DccReissuanceConsentViewModel = viewModel
override fun create(groupKey: String): DccReissuanceConsentViewModel = viewModel
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class DccReissuanceAccCertsFragmentTest : BaseUITest() {
}
}

private val args = DccReissuanceAccCertsFragmentArgs(personIdentifierCode = "personIdentifierCode").toBundle()
private val args = DccReissuanceAccCertsFragmentArgs(groupKey = "personIdentifierCode").toBundle()

private val certificateList = mutableListOf<DccReissuanceItem>(
DccReissuanceCertificateCard.Item(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class BoosterNotificationService @Inject constructor(
personCertificatesSettings.setBoosterNotifiedAt(personIdentifier, timeStamper.nowUTC)
Timber.tag(TAG).d("Person %s notified about booster rule change", codeSHA256)
} else {
personCertificatesSettings.acknowledgeBoosterRule(personIdentifier, newRuleId)
Timber.tag(TAG).d("Person %s shouldn't be notified about booster rule=%s", codeSHA256, newRuleId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class BoosterInfoDetailsFragment : Fragment(R.layout.fragment_booster_informatio
constructorCall = { factory, _ ->
factory as BoosterInfoDetailsViewModel.Factory
factory.create(
personIdentifierCode = args.personIdentifierCode,
groupKey = args.groupKey,
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ import timber.log.Timber
class BoosterInfoDetailsViewModel @AssistedInject constructor(
dispatcherProvider: DispatcherProvider,
personCertificatesProvider: PersonCertificatesProvider,
@Assisted private val personIdentifierCode: String,
@Assisted private val groupKey: String,
private val format: CclTextFormatter,
private val personCertificatesSettings: PersonCertificatesSettings,
) : CWAViewModel(dispatcherProvider) {

val shouldClose = SingleLiveEvent<Unit>()

private val uiStateFlow =
personCertificatesProvider.findPersonByIdentifierCode(personIdentifierCode).map { person ->
personCertificatesProvider.findPersonByIdentifierCode(groupKey).map { person ->
val boosterNotification = person!!.dccWalletInfo!!.boosterNotification
boosterNotification.identifier?.let { id ->
personCertificatesSettings.acknowledgeBoosterRule(
Expand All @@ -42,7 +42,7 @@ class BoosterInfoDetailsViewModel @AssistedInject constructor(
)
}.catch { error ->
// This should never happen due to checks on previous screen
Timber.d(error, "No person found for $personIdentifierCode")
Timber.d(error, "No person found for $groupKey")
shouldClose.postValue(Unit)
}
val uiState = uiStateFlow.asLiveData2()
Expand All @@ -57,7 +57,7 @@ class BoosterInfoDetailsViewModel @AssistedInject constructor(
@AssistedFactory
interface Factory : CWAViewModelFactory<BoosterInfoDetailsViewModel> {
fun create(
personIdentifierCode: String
groupKey: String
): BoosterInfoDetailsViewModel
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class PersonNotificationSender @Inject constructor(
}

private fun buildPendingIntent(personIdentifier: CertificatePersonIdentifier): PendingIntent {
val args = PersonDetailsFragmentArgs(personCode = personIdentifier.codeSHA256).toBundle()
val args = PersonDetailsFragmentArgs(groupKey = personIdentifier.groupingKey).toBundle()
return deepLinkBuilderFactory.create(context)
.setGraph(R.navigation.nav_graph)
.setComponentName(LauncherActivity::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,52 +34,48 @@ class PersonCertificatesProvider @Inject constructor(
personCertificatesSettings.personsSettings
) { certificateContainer, cwaUser, personWallets, personsSettings ->

val personWalletsGroup = personWallets.associateBy { it.personGroupKey }
val groupedCerts = certificateContainer.allCwaCertificates.groupByPerson()
val wallets = personWallets.associateBy { it.personGroupKey }
val groupedCerts = certificateContainer.allCwaCertificates.groupByPerson().filterNot { it.isEmpty() }

if (cwaUser != null && groupedCerts.findCertificatesForPerson(cwaUser).isEmpty()) {
Timber.tag(TAG).v("Resetting cwa user")
personCertificatesSettings.removeCurrentCwaUser()
}

groupedCerts
.filterNot { certs ->
certs.isEmpty() // Any person should have at least one certificate to show up in the list
}.map { certs ->
val (personIdentifier: CertificatePersonIdentifier, dccWalletInfo: DccWalletInfo?) =
certs.toCertificateSortOrder().firstNotNullOfOrNull {
personWalletsGroup[it.personIdentifier.groupingKey]?.let { walletGroup ->
it.personIdentifier to walletGroup.dccWalletInfo
}
} ?: (certs.identifier to null)

val settings = personsSettings[personIdentifier]

Timber.tag(TAG).v(
"Person [code=%s, certsCount=%d, walletExist=%s, settings=%s]",
personIdentifier.codeSHA256,
certs.size,
dccWalletInfo != null,
settings
)

val hasBoosterBadge = settings.hasBoosterBadge(dccWalletInfo?.boosterNotification)
val hasDccReissuanceBadge = settings.hasReissuanceBadge(dccWalletInfo)
val hasNewAdmissionStateBadge = settings.hasAdmissionStateChangedBadge()
val badgeCount = certs.count { it.hasNotificationBadge } +
hasBoosterBadge.toInt() + hasDccReissuanceBadge.toInt() + hasNewAdmissionStateBadge.toInt()
Timber.tag(TAG).d("Person [code=%s, badgeCount=%s]", personIdentifier.codeSHA256, badgeCount)

PersonCertificates(
certificates = certs.toCertificateSortOrder(),
isCwaUser = certs.any { it.personIdentifier.belongsToSamePerson(cwaUser) },
badgeCount = badgeCount,
dccWalletInfo = dccWalletInfo,
hasBoosterBadge = hasBoosterBadge,
hasDccReissuanceBadge = hasDccReissuanceBadge,
hasNewAdmissionState = hasNewAdmissionStateBadge
)
}.toSet()
groupedCerts.map { certs ->
val sortedCerts = certs.toCertificateSortOrder()
val identifier = certs.identifier
val dccWalletInfo = sortedCerts.findWalletInfo(wallets)
val settings = sortedCerts.findSettings(personsSettings, identifier)

Timber.tag(TAG).v(
"Person [code=%s, certsCount=%d, walletExist=%s, settings=%s]",
identifier.codeSHA256,
certs.size,
dccWalletInfo != null,
settings
)

val hasBoosterBadge = settings.hasBoosterBadge(dccWalletInfo?.boosterNotification)
val hasDccReissuanceBadge = settings.hasReissuanceBadge(dccWalletInfo)
val hasNewAdmissionStateBadge = settings.hasAdmissionStateChangedBadge()
val badgeCount = certs.count { it.hasNotificationBadge } +
hasBoosterBadge.toInt() +
hasDccReissuanceBadge.toInt() +
hasNewAdmissionStateBadge.toInt()

Timber.tag(TAG).d("Person [code=%s, badgeCount=%s]", identifier.codeSHA256, badgeCount)

PersonCertificates(
certificates = sortedCerts,
isCwaUser = certs.any { it.personIdentifier.belongsToSamePerson(cwaUser) },
badgeCount = badgeCount,
dccWalletInfo = dccWalletInfo,
hasBoosterBadge = hasBoosterBadge,
hasDccReissuanceBadge = hasDccReissuanceBadge,
hasNewAdmissionState = hasNewAdmissionStateBadge
)
}.toSet()
}.shareLatest(scope = appScope)

/**
Expand All @@ -95,20 +91,20 @@ class PersonCertificatesProvider @Inject constructor(
val personsBadgeCount: Flow<Int> = personCertificates.map { persons -> persons.sumOf { it.badgeCount } }

/**
* Find specific person by [CertificatePersonIdentifier.codeSHA256]
* @param personIdentifierCode [String]
* Find specific person by [CertificatePersonIdentifier.groupingKey]
* @param groupKey [String]
*/
fun findPersonByIdentifierCode(personIdentifierCode: String): Flow<PersonCertificates?> =
fun findPersonByIdentifierCode(groupKey: String): Flow<PersonCertificates?> =
personCertificates.map { persons ->
persons.find { it.personIdentifier.codeSHA256 == personIdentifierCode }
persons.find { it.personIdentifier.belongsToSamePerson(groupKey.toIdentifier()) }
}

private fun PersonSettings?.hasBoosterBadge(boosterNotification: BoosterNotification?): Boolean {
if (boosterNotification == null || !boosterNotification.visible) return false
return hasNotSeenBoosterRuleYet(this, boosterNotification)
}

fun hasNotSeenBoosterRuleYet(
private fun hasNotSeenBoosterRuleYet(
personSettings: PersonSettings?,
boosterNotification: BoosterNotification
) = personSettings?.lastSeenBoosterRuleIdentifier != boosterNotification.identifier
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.rki.coronawarnapp.covidcertificate.person.core

import de.rki.coronawarnapp.ccl.dccwalletinfo.model.PersonWalletInfo
import de.rki.coronawarnapp.covidcertificate.common.certificate.CertificatePersonIdentifier
import de.rki.coronawarnapp.covidcertificate.common.certificate.CwaCovidCertificate
import de.rki.coronawarnapp.covidcertificate.person.model.PersonSettings

internal fun List<CwaCovidCertificate>.findSettings(
personsSettings: Map<CertificatePersonIdentifier, PersonSettings>,
identifier: CertificatePersonIdentifier,
) = personsSettings[identifier] ?: firstNotNullOfOrNull { cert ->
personsSettings.entries.firstOrNull { entry -> cert.personIdentifier.belongsToSamePerson(entry.key) }?.value
}

internal fun List<CwaCovidCertificate>.findWalletInfo(
wallets: Map<String, PersonWalletInfo>
) = firstNotNullOfOrNull { wallets[it.personIdentifier.groupingKey]?.dccWalletInfo } ?: firstNotNullOfOrNull { cert ->
wallets.entries.firstOrNull { entry ->
cert.personIdentifier.belongsToSamePerson(entry.key.toIdentifier())
}?.value?.dccWalletInfo
}

internal fun String.toIdentifier(): CertificatePersonIdentifier =
split("#").run {
CertificatePersonIdentifier(
dateOfBirthFormatted = get(0),
lastNameStandardized = getOrNull(1),
firstNameStandardized = getOrNull(2),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ data class OpenRecoveryCertificateDetails(

data class ValidationStart(val containerId: CertificateContainerId) : PersonDetailsEvents()
data class ShowErrorDialog(val error: Throwable) : PersonDetailsEvents()
data class OpenBoosterInfoDetails(val personIdentifierCode: String) : PersonDetailsEvents()
data class OpenCertificateReissuanceConsent(val personIdentifierCode: String) : PersonDetailsEvents()
data class OpenBoosterInfoDetails(val groupKey: String) : PersonDetailsEvents()
data class OpenCertificateReissuanceConsent(val groupKey: String) : PersonDetailsEvents()
data class RecycleCertificate(
val cwaCovidCertificate: CwaCovidCertificate,
val position: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class PersonDetailsFragment : Fragment(R.layout.person_details_fragment), AutoIn
constructorCall = { factory, _ ->
factory as PersonDetailsViewModel.Factory
factory.create(
personIdentifierCode = args.personCode,
groupKey = args.groupKey,
colorShade = args.colorShade
)
}
Expand All @@ -65,7 +65,7 @@ class PersonDetailsFragment : Fragment(R.layout.person_details_fragment), AutoIn
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
root.transitionName = args.personCode
root.transitionName = args.groupKey
toolbar.setNavigationOnClickListener {
viewModel.dismissAdmissionStateBadge(true)
}
Expand Down Expand Up @@ -150,12 +150,12 @@ class PersonDetailsFragment : Fragment(R.layout.person_details_fragment), AutoIn

is OpenBoosterInfoDetails -> findNavController().navigate(
PersonDetailsFragmentDirections
.actionPersonDetailsFragmentToBoosterInfoDetailsFragment(event.personIdentifierCode)
.actionPersonDetailsFragmentToBoosterInfoDetailsFragment(event.groupKey)
).also { viewModel.dismissAdmissionStateBadge() }

is OpenCertificateReissuanceConsent -> findNavController().navigate(
PersonDetailsFragmentDirections
.actionPersonDetailsFragmentToDccReissuanceConsentFragment(event.personIdentifierCode)
.actionPersonDetailsFragmentToDccReissuanceConsentFragment(event.groupKey)
).also { viewModel.dismissAdmissionStateBadge() }

Back -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import de.rki.coronawarnapp.covidcertificate.person.core.PersonCertificatesProvi
import de.rki.coronawarnapp.covidcertificate.person.core.PersonCertificatesSettings
import de.rki.coronawarnapp.covidcertificate.person.core.isHighestCertificateDisplayValid
import de.rki.coronawarnapp.covidcertificate.person.core.isMaskOptional
import de.rki.coronawarnapp.covidcertificate.person.core.toIdentifier
import de.rki.coronawarnapp.covidcertificate.person.ui.details.items.AdmissionStatusCard
import de.rki.coronawarnapp.covidcertificate.person.ui.details.items.BoosterCard
import de.rki.coronawarnapp.covidcertificate.person.ui.details.items.CertificateItem
Expand Down Expand Up @@ -55,7 +56,7 @@ class PersonDetailsViewModel @AssistedInject constructor(
private val personCertificatesSettings: PersonCertificatesSettings,
private val dccValidationRepository: DccValidationRepository,
private val recycledCertificatesProvider: RecycledCertificatesProvider,
@Assisted private val personIdentifierCode: String,
@Assisted private val groupKey: String,
@Assisted private val colorShade: PersonColorShade,
private val format: CclTextFormatter,
) : CWAViewModel(dispatcherProvider) {
Expand All @@ -66,9 +67,9 @@ class PersonDetailsViewModel @AssistedInject constructor(

private val loadingButtonState = MutableStateFlow(false)
private val personCertificatesFlow = personCertificatesProvider.personCertificates.mapNotNull { certificateSet ->
certificateSet.first { it.personIdentifier.codeSHA256 == personIdentifierCode }
certificateSet.first { it.personIdentifier.belongsToSamePerson(groupKey.toIdentifier()) }
}.catch { error ->
Timber.d(error, "No person found for $personIdentifierCode")
Timber.d(error, "No person found for $groupKey")
events.postValue(Back)
}

Expand Down Expand Up @@ -203,7 +204,7 @@ class PersonDetailsViewModel @AssistedInject constructor(
title = format(boosterNotification.titleText),
subtitle = format(boosterNotification.subtitleText),
badgeVisible = personCertificates.hasBoosterBadge,
onClick = { events.postValue(OpenBoosterInfoDetails(personIdentifierCode)) }
onClick = { events.postValue(OpenBoosterInfoDetails(groupKey)) }
)

private suspend fun dccReissuanceItem(
Expand All @@ -213,7 +214,7 @@ class PersonDetailsViewModel @AssistedInject constructor(
title = format(division.titleText),
subtitle = format(division.subtitleText),
badgeVisible = personCertificates.hasDccReissuanceBadge,
onClick = { events.postValue(OpenCertificateReissuanceConsent(personIdentifierCode)) }
onClick = { events.postValue(OpenCertificateReissuanceConsent(groupKey)) }
)

private fun onValidateCertificate(containerId: CertificateContainerId) = launch {
Expand Down Expand Up @@ -316,7 +317,7 @@ class PersonDetailsViewModel @AssistedInject constructor(

fun dismissAdmissionStateBadge(shouldPopBackstack: Boolean = false) {
viewModelScope.launch {
personCertificatesProvider.findPersonByIdentifierCode(personIdentifierCode)
personCertificatesProvider.findPersonByIdentifierCode(groupKey)
.firstOrNull()?.personIdentifier
?.let { personCertificatesSettings.dismissGStatusBadge(it) }
if (shouldPopBackstack) events.postValue(Back)
Expand All @@ -341,7 +342,7 @@ class PersonDetailsViewModel @AssistedInject constructor(
@AssistedFactory
interface Factory : CWAViewModelFactory<PersonDetailsViewModel> {
fun create(
personIdentifierCode: String,
groupKey: String,
colorShade: PersonColorShade,
): PersonDetailsViewModel
}
Expand Down
Loading