From ac0016331c14598e5102d2a405f6740f7fd57514 Mon Sep 17 00:00:00 2001 From: Matthias Urhahn <matthias.urhahn@sap.com> Date: Fri, 9 Apr 2021 16:59:27 +0200 Subject: [PATCH] Adjust EW/PT riskresult combination (EXPOSUREAPP-6169) (#2788) * Refactoring, move extensions closer to their use-case. * Adjust riskState combination, if either calculation fails, the combined riskstate is also CALCULATION_FAILED and should show the white failure card on the home screen. * Introduce ptRiskLevelResult.checkInOverlapCount as analog to ewRiskLevelResult.matchedKeyCount for the CombinedEwPtRiskLevelResult to allow the UI show information for "low risk with encounters" situations. * LINTs * Adjust unit tests to reflect the risk status combination priority (FAILED>HIGH>LOW). * Home screen download/calculation progress should include PresenceTracingWarningTask * LINTs Co-authored-by: Lukas Lechner <lukas.lechner@sap.com> Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> --- .../coronawarnapp/ui/tracing/TracingData.kt | 8 +- .../presencetracing/risk/PtRiskLevelResult.kt | 12 ++- .../risk/calculation/CheckInWarningMatcher.kt | 4 + .../calculation/PresenceTracingConversions.kt | 16 ---- .../calculation/PresenceTracingRiskMapper.kt | 1 + .../execution/PresenceTracingWarningTask.kt | 16 ++-- .../PresenceTracingWarningTaskProgress.kt | 16 ++++ .../storage/PresenceTracingRiskRepository.kt | 32 +++++-- .../coronawarnapp/risk/CombinedEwPtRisk.kt | 13 ++- .../de/rki/coronawarnapp/risk/RiskState.kt | 10 +++ .../risk/storage/BaseRiskLevelStorage.kt | 13 +-- .../storage/TracingRepository.kt | 44 ++++++++-- .../coronawarnapp/tracing/TracingProgress.kt | 12 ++- .../tracing/states/TracingState.kt | 4 +- .../ui/details/TracingDetailsItemProvider.kt | 4 +- .../items/riskdetails/DetailsLowRiskBox.kt | 7 +- ...cing_details_item_riskdetails_low_view.xml | 1 + .../risk/storage/BaseRiskLevelStorageTest.kt | 10 +-- .../risk/storage/CombineRiskTest.kt | 85 +++++++++++++------ .../details/TracingDetailsItemProviderTest.kt | 2 +- 20 files changed, 216 insertions(+), 94 deletions(-) delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingConversions.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskProgress.kt diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt index c1b30a16e..44c7ad447 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt @@ -48,7 +48,7 @@ object TracingData { daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_INACTIVE ), - DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) + DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedRiskCount = 0) ) ) @@ -79,7 +79,7 @@ object TracingData { daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), - DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) + DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedRiskCount = 0) ) ) @@ -110,7 +110,7 @@ object TracingData { daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), - DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) + DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedRiskCount = 0) ) ) @@ -141,7 +141,7 @@ object TracingData { daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), - DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) + DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedRiskCount = 0) ) ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt index 62b25b760..541496f3b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt @@ -1,15 +1,20 @@ package de.rki.coronawarnapp.presencetracing.risk +import de.rki.coronawarnapp.presencetracing.risk.calculation.CheckInWarningOverlap import de.rki.coronawarnapp.presencetracing.risk.calculation.PresenceTracingDayRisk import de.rki.coronawarnapp.risk.RiskState import org.joda.time.Instant import org.joda.time.LocalDate +/** + * @param presenceTracingDayRisk Only available for the last calculation, if successful, otherwise null + * @param checkInWarningOverlaps Only available for the last calculation, if successful, otherwise null + */ data class PtRiskLevelResult( val calculatedAt: Instant, val riskState: RiskState, - // only available for the last calculation if successful, otherwise null - val presenceTracingDayRisk: List<PresenceTracingDayRisk>? = null + val presenceTracingDayRisk: List<PresenceTracingDayRisk>? = null, + private val checkInWarningOverlaps: List<CheckInWarningOverlap>? = null, ) { val wasSuccessfullyCalculated: Boolean @@ -39,4 +44,7 @@ data class PtRiskLevelResult( RiskState.LOW_RISK -> numberOfDaysWithLowRisk else -> 0 } + + val checkInOverlapCount: Int + get() = checkInWarningOverlaps?.size ?: 0 } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcher.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcher.kt index 157a55a75..8dce26bef 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcher.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcher.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext import org.joda.time.Instant import timber.log.Timber import java.lang.reflect.Modifier.PRIVATE +import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.coroutines.CoroutineContext @@ -143,4 +144,7 @@ internal fun CheckIn.calculateOverlap( ) } +// converts number of 10min intervals into milliseconds +internal fun Int.tenMinIntervalToMillis() = this * TimeUnit.MINUTES.toMillis(10L) + private const val TAG = "CheckInWarningMatcher" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingConversions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingConversions.kt deleted file mode 100644 index 4e529779d..000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingConversions.kt +++ /dev/null @@ -1,16 +0,0 @@ -package de.rki.coronawarnapp.presencetracing.risk.calculation - -import de.rki.coronawarnapp.risk.RiskState -import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel -import java.util.concurrent.TimeUnit - -// converts number of 10min intervals into milliseconds -internal fun Int.tenMinIntervalToMillis() = this * TimeUnit.MINUTES.toMillis(10L) - -fun RiskLevel.mapToRiskState(): RiskState { - return when (this) { - RiskLevel.UNSPECIFIED, RiskLevel.UNRECOGNIZED -> RiskState.CALCULATION_FAILED - RiskLevel.LOW -> RiskState.LOW_RISK - RiskLevel.HIGH -> RiskState.INCREASED_RISK - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt index 1cebd0aec..06b72ed46 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt @@ -4,6 +4,7 @@ import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.PresenceTracingRiskCalculationParamContainer import de.rki.coronawarnapp.risk.DefaultRiskLevels.Companion.inRange import de.rki.coronawarnapp.risk.RiskState +import de.rki.coronawarnapp.risk.mapToRiskState import kotlinx.coroutines.flow.first import timber.log.Timber import javax.inject.Inject diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt index db128bc5d..8000745d6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt @@ -12,7 +12,6 @@ import de.rki.coronawarnapp.presencetracing.warning.storage.TraceWarningReposito import de.rki.coronawarnapp.task.Task import de.rki.coronawarnapp.task.TaskCancellationException import de.rki.coronawarnapp.task.TaskFactory -import de.rki.coronawarnapp.task.common.DefaultProgress import de.rki.coronawarnapp.util.TimeStamper import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.flow.Flow @@ -31,10 +30,10 @@ class PresenceTracingWarningTask @Inject constructor( private val presenceTracingRiskRepository: PresenceTracingRiskRepository, private val traceWarningRepository: TraceWarningRepository, private val checkInsRepository: CheckInRepository, -) : Task<DefaultProgress, PresenceTracingWarningTask.Result> { +) : Task<PresenceTracingWarningTaskProgress, PresenceTracingWarningTask.Result> { - private val internalProgress = ConflatedBroadcastChannel<DefaultProgress>() - override val progress: Flow<DefaultProgress> = internalProgress.asFlow() + private val internalProgress = ConflatedBroadcastChannel<PresenceTracingWarningTaskProgress>() + override val progress: Flow<PresenceTracingWarningTaskProgress> = internalProgress.asFlow() private var isCanceled = false @@ -61,6 +60,9 @@ class PresenceTracingWarningTask @Inject constructor( val nowUTC = timeStamper.nowUTC checkCancel() + Timber.tag(TAG).d("Syncing packages.") + internalProgress.send(PresenceTracingWarningTaskProgress.Downloading()) + val syncResult = syncTool.syncPackages() if (syncResult.successful) { @@ -97,6 +99,8 @@ class PresenceTracingWarningTask @Inject constructor( } Timber.tag(TAG).d("Running check-in matcher.") + internalProgress.send(PresenceTracingWarningTaskProgress.Calculating()) + val matcherResult = checkInWarningMatcher.process( checkIns = checkIns, warningPackages = unprocessedPackages, @@ -147,13 +151,13 @@ class PresenceTracingWarningTask @Inject constructor( class Factory @Inject constructor( private val taskByDagger: Provider<PresenceTracingWarningTask>, private val appConfigProvider: AppConfigProvider - ) : TaskFactory<DefaultProgress, Task.Result> { + ) : TaskFactory<PresenceTracingWarningTaskProgress, Task.Result> { override suspend fun createConfig(): TaskFactory.Config = Config( executionTimeout = appConfigProvider.getAppConfig().overallDownloadTimeout ) - override val taskProvider: () -> Task<DefaultProgress, Task.Result> = { + override val taskProvider: () -> Task<PresenceTracingWarningTaskProgress, Task.Result> = { taskByDagger.get() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskProgress.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskProgress.kt new file mode 100644 index 000000000..a7c986995 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskProgress.kt @@ -0,0 +1,16 @@ +package de.rki.coronawarnapp.presencetracing.risk.execution + +import de.rki.coronawarnapp.task.Task +import de.rki.coronawarnapp.util.ui.CachedString +import de.rki.coronawarnapp.util.ui.LazyString + +sealed class PresenceTracingWarningTaskProgress : Task.Progress { + + data class Downloading( + override val primaryMessage: LazyString = CachedString { "" } + ) : PresenceTracingWarningTaskProgress() + + data class Calculating( + override val primaryMessage: LazyString = CachedString { "" } + ) : PresenceTracingWarningTaskProgress() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt index daf5c60bc..9dbccbc3e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt @@ -128,9 +128,15 @@ class PresenceTracingRiskRepository @Inject constructor( if (!lastSuccessfulFound && entity.riskState != RiskState.CALCULATION_FAILED) { lastSuccessfulFound = true // add risk per day to the last successful result - entity.toCheckInWarningOverlap(presenceTracingDayRisk.first()) + entity.toRiskLevelResult( + presenceTracingDayRisks = presenceTracingDayRisk.first(), + checkInWarningOverlaps = checkInWarningOverlaps.first(), + ) } else { - entity.toCheckInWarningOverlap(null) + entity.toRiskLevelResult( + presenceTracingDayRisks = null, + checkInWarningOverlaps = null, + ) } } } @@ -144,16 +150,22 @@ class PresenceTracingRiskRepository @Inject constructor( if (!lastSuccessfulFound && entity.riskState != RiskState.CALCULATION_FAILED) { lastSuccessfulFound = true // add risk per day to the last successful result - entity.toCheckInWarningOverlap(presenceTracingDayRisk.first()) + entity.toRiskLevelResult( + presenceTracingDayRisks = presenceTracingDayRisk.first(), + checkInWarningOverlaps = checkInWarningOverlaps.first(), + ) } else { - entity.toCheckInWarningOverlap(null) + entity.toRiskLevelResult( + presenceTracingDayRisks = null, + checkInWarningOverlaps = null, + ) } } } private fun addResult(result: PtRiskLevelResult) { Timber.i("Saving risk calculation from ${result.calculatedAt} with result ${result.riskState}.") - riskLevelResultDao.insert(result.toTraceTimeIntervalMatchEntity()) + riskLevelResultDao.insert(result.toRiskLevelEntity()) } suspend fun clearAllTables() { @@ -242,15 +254,17 @@ data class PresenceTracingRiskLevelResultEntity( @ColumnInfo(name = "riskStateCode") val riskState: RiskState ) -private fun PresenceTracingRiskLevelResultEntity.toCheckInWarningOverlap( - presenceTracingDayRisk: List<PresenceTracingDayRisk>? +private fun PresenceTracingRiskLevelResultEntity.toRiskLevelResult( + presenceTracingDayRisks: List<PresenceTracingDayRisk>?, + checkInWarningOverlaps: List<CheckInWarningOverlap>? ) = PtRiskLevelResult( calculatedAt = Instant.ofEpochMilli((calculatedAtMillis)), riskState = riskState, - presenceTracingDayRisk = presenceTracingDayRisk + presenceTracingDayRisk = presenceTracingDayRisks, + checkInWarningOverlaps = checkInWarningOverlaps, ) -private fun PtRiskLevelResult.toTraceTimeIntervalMatchEntity() = PresenceTracingRiskLevelResultEntity( +private fun PtRiskLevelResult.toRiskLevelEntity() = PresenceTracingRiskLevelResultEntity( calculatedAtMillis = calculatedAt.millis, riskState = riskState ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/CombinedEwPtRisk.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/CombinedEwPtRisk.kt index b4e6305fb..1e2a96561 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/CombinedEwPtRisk.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/CombinedEwPtRisk.kt @@ -13,8 +13,8 @@ data class CombinedEwPtDayRisk( ) data class CombinedEwPtRiskLevelResult( - val ptRiskLevelResult: PtRiskLevelResult, - val ewRiskLevelResult: EwRiskLevelResult + private val ptRiskLevelResult: PtRiskLevelResult, + private val ewRiskLevelResult: EwRiskLevelResult ) { val riskState: RiskState by lazy { @@ -56,6 +56,15 @@ data class CombinedEwPtRiskLevelResult( else -> null } } + + /** + * The combination of matched exposure windows and overlaps. + * If we have matches > 0, but are still in a low risk state, + * the UI displays additional information in the risk details screen. + */ + val matchedRiskCount: Int by lazy { + ewRiskLevelResult.matchedKeyCount + ptRiskLevelResult.checkInOverlapCount + } } data class LastCombinedRiskResults( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskState.kt index e71d6f7a4..facbcb948 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskState.kt @@ -1,7 +1,17 @@ package de.rki.coronawarnapp.risk +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel + enum class RiskState { LOW_RISK, INCREASED_RISK, CALCULATION_FAILED } + +fun RiskLevel.mapToRiskState(): RiskState { + return when (this) { + RiskLevel.UNSPECIFIED, RiskLevel.UNRECOGNIZED -> RiskState.CALCULATION_FAILED + RiskLevel.LOW -> RiskState.LOW_RISK + RiskLevel.HIGH -> RiskState.INCREASED_RISK + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt index d5d0d71f0..af277a073 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt @@ -5,7 +5,6 @@ import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult import de.rki.coronawarnapp.presencetracing.risk.TraceLocationCheckInRisk import de.rki.coronawarnapp.presencetracing.risk.calculation.PresenceTracingDayRisk -import de.rki.coronawarnapp.presencetracing.risk.calculation.mapToRiskState import de.rki.coronawarnapp.presencetracing.risk.storage.PresenceTracingRiskRepository import de.rki.coronawarnapp.risk.CombinedEwPtDayRisk import de.rki.coronawarnapp.risk.CombinedEwPtRiskLevelResult @@ -13,6 +12,7 @@ import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.EwRiskLevelTaskResult import de.rki.coronawarnapp.risk.LastCombinedRiskResults import de.rki.coronawarnapp.risk.RiskState +import de.rki.coronawarnapp.risk.mapToRiskState import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase @@ -256,10 +256,13 @@ internal fun combineRisk( } } -internal fun combine(left: RiskState?, right: RiskState?): RiskState { - return if (left == RiskState.INCREASED_RISK || right == RiskState.INCREASED_RISK) RiskState.INCREASED_RISK - else if (left == RiskState.LOW_RISK || right == RiskState.LOW_RISK) RiskState.LOW_RISK - else RiskState.CALCULATION_FAILED +internal fun combine(vararg states: RiskState?): RiskState { + if (states.any { it == RiskState.CALCULATION_FAILED }) return RiskState.CALCULATION_FAILED + if (states.any { it == RiskState.INCREASED_RISK }) return RiskState.INCREASED_RISK + + require(states.filterNotNull().all { it == RiskState.LOW_RISK }) + + return RiskState.LOW_RISK } internal fun max(left: Instant, right: Instant): Instant { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt index 49488cc0e..6f9fdeed6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt @@ -1,11 +1,14 @@ package de.rki.coronawarnapp.storage +import android.annotation.SuppressLint import android.content.Context import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.lastSubmission +import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingWarningTask +import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingWarningTaskProgress import de.rki.coronawarnapp.risk.RiskLevelTask import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.task.TaskInfo @@ -21,7 +24,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.joda.time.Duration import timber.log.Timber @@ -45,26 +48,49 @@ class TracingRepository @Inject constructor( private val backgroundModeStatus: BackgroundModeStatus ) { + @SuppressLint("BinaryOperationInTimber") val tracingProgress: Flow<TracingProgress> = combine( - taskController.tasks.map { it.isDownloadDiagnosisKeysTaskRunning() }, + taskController.tasks, enfClient.isPerformingExposureDetection(), - taskController.tasks.map { it.isRiskLevelTaskRunning() } - ) { isDownloading, isExposureDetecting, isRiskLeveling -> + ) { taskInfos, isExposureDetecting -> + + val isEWDownloading = taskInfos.isEWDownloadingPackages() + val isEWCalculatingRisk = taskInfos.isEWCalculatingRisk() + + val isPTDownloading = taskInfos.isPTDownloadingPackages() + val isPTCalculatingRisk = taskInfos.isPTCalculatingRisk() + when { - isDownloading -> TracingProgress.Downloading - isExposureDetecting || isRiskLeveling -> TracingProgress.ENFIsCalculating + isEWDownloading || isPTDownloading -> TracingProgress.Downloading + isExposureDetecting || isEWCalculatingRisk || isPTCalculatingRisk -> TracingProgress.IsCalculating else -> TracingProgress.Idle + }.also { + Timber.tag(TAG).v( + "TracingProgress: $it, isExposureDetecting=$isExposureDetecting, " + + "isEWDownloading=$isEWDownloading, isEWCalculatingRisk=$isEWCalculatingRisk, " + + "isPTDownloading=$isPTDownloading, isPTCalculatingRisk=$isPTCalculatingRisk" + ) } } - private fun List<TaskInfo>.isRiskLevelTaskRunning() = any { - it.taskState.isActive && it.taskState.request.type == RiskLevelTask::class + private suspend fun List<TaskInfo>.isPTDownloadingPackages() = any { + it.taskState.isActive && it.taskState.request.type == PresenceTracingWarningTask::class && + it.progress.firstOrNull() is PresenceTracingWarningTaskProgress.Downloading } - private fun List<TaskInfo>.isDownloadDiagnosisKeysTaskRunning() = any { + private suspend fun List<TaskInfo>.isPTCalculatingRisk() = any { + it.taskState.isActive && it.taskState.request.type == PresenceTracingWarningTask::class && + it.progress.firstOrNull() is PresenceTracingWarningTaskProgress.Calculating + } + + private fun List<TaskInfo>.isEWDownloadingPackages() = any { it.taskState.isActive && it.taskState.request.type == DownloadDiagnosisKeysTask::class } + private fun List<TaskInfo>.isEWCalculatingRisk() = any { + it.taskState.isActive && it.taskState.request.type == RiskLevelTask::class + } + /** * Refresh the diagnosis keys. For that isRefreshing is set to true which is displayed in the ui. * Afterwards the RetrieveDiagnosisKeysTransaction and the RiskLevelTransaction are started. diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/TracingProgress.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/TracingProgress.kt index aa4416de0..955d17a6a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/TracingProgress.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/TracingProgress.kt @@ -2,9 +2,15 @@ package de.rki.coronawarnapp.tracing sealed class TracingProgress { - object Idle : TracingProgress() + object Idle : TracingProgress() { + override fun toString(): String = "TracingProgress.Idle" + } - object Downloading : TracingProgress() + object Downloading : TracingProgress() { + override fun toString(): String = "TracingProgress.Downloading" + } - object ENFIsCalculating : TracingProgress() + object IsCalculating : TracingProgress() { + override fun toString(): String = "TracingProgress.IsCalculating" + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt index a12be64f0..58cf7f537 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt @@ -226,13 +226,13 @@ data class TracingInProgress( fun getProgressCardHeadline(c: Context): String = when (tracingProgress) { TracingProgress.Downloading -> R.string.risk_card_progress_download_headline - TracingProgress.ENFIsCalculating -> R.string.risk_card_progress_calculation_headline + TracingProgress.IsCalculating -> R.string.risk_card_progress_calculation_headline TracingProgress.Idle -> null }?.let { c.getString(it) } ?: "" fun getProgressCardBody(c: Context): String = when (tracingProgress) { TracingProgress.Downloading -> R.string.risk_card_progress_download_body - TracingProgress.ENFIsCalculating -> R.string.risk_card_progress_calculation_body + TracingProgress.IsCalculating -> R.string.risk_card_progress_calculation_body TracingProgress.Idle -> null }?.let { c.getString(it) } ?: "" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt index f47238e68..7537905dc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt @@ -47,7 +47,7 @@ class TracingDetailsItemProvider @Inject constructor( mutableListOf<DetailsItem>().apply { if (status != Status.TRACING_INACTIVE && latestCalc.riskState == RiskState.LOW_RISK && - latestCalc.ewRiskLevelResult.matchedKeyCount > 0 + latestCalc.matchedRiskCount > 0 ) { add(AdditionalInfoLowRiskBox.Item) } @@ -81,7 +81,7 @@ class TracingDetailsItemProvider @Inject constructor( } latestCalc.riskState == RiskState.LOW_RISK -> DetailsLowRiskBox.Item( riskState = latestCalc.riskState, - matchedKeyCount = latestCalc.ewRiskLevelResult.matchedKeyCount + matchedRiskCount = latestCalc.matchedRiskCount ) latestCalc.riskState == RiskState.INCREASED_RISK -> DetailsIncreasedRiskBox.Item( riskState = latestCalc.riskState, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsLowRiskBox.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsLowRiskBox.kt index 574698d97..d3c84c6c0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsLowRiskBox.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsLowRiskBox.kt @@ -33,7 +33,8 @@ class DetailsLowRiskBox( payloads: List<Any> ) -> Unit = { item, _ -> info = item - if (item.matchedKeyCount > 0) { + // Low risk, but matched risk item? More info! Don't worry! + if (item.matchedRiskCount > 0) { riskDetailsInformationLowriskBodyUrl.visibility = View.VISIBLE riskDetailsInformationLowriskBodyUrl.convertToHyperlink( context.getString(R.string.risk_details_explanation_faq_link) @@ -46,13 +47,13 @@ class DetailsLowRiskBox( data class Item( val riskState: RiskState, - val matchedKeyCount: Int + val matchedRiskCount: Int ) : RiskDetailsStateItem { fun getRiskDetailsRiskLevelBody(c: Context): String { // TODO consider pt encounters? return c.getString( - if (matchedKeyCount > 0) R.string.risk_details_information_body_low_risk_with_encounter + if (matchedRiskCount > 0) R.string.risk_details_information_body_low_risk_with_encounter else R.string.risk_details_information_body_low_risk ) } diff --git a/Corona-Warn-App/src/main/res/layout/tracing_details_item_riskdetails_low_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_details_item_riskdetails_low_view.xml index c18fabd78..47d969b24 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_details_item_riskdetails_low_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_details_item_riskdetails_low_view.xml @@ -60,6 +60,7 @@ android:clickable="true" android:focusable="true" android:linksClickable="true" + tools:visibility="visible" android:visibility="gone" android:text="@string/risk_details_explanation_dialog_faq_body" app:layout_constraintEnd_toEndOf="parent" diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt index 03c675890..fdff4062d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt @@ -220,7 +220,7 @@ class BaseRiskLevelStorageTest : BaseTest() { riskLevelResults.size shouldBe 2 riskLevelResults[0].calculatedAt shouldBe calculatedAt - riskLevelResults[0].riskState shouldBe RiskState.INCREASED_RISK + riskLevelResults[0].riskState shouldBe RiskState.CALCULATION_FAILED riskLevelResults[1].calculatedAt shouldBe ewCalculatedAt riskLevelResults[1].riskState shouldBe RiskState.INCREASED_RISK @@ -241,7 +241,7 @@ class BaseRiskLevelStorageTest : BaseTest() { PtRiskLevelResult( calculatedAt = calculatedAt, presenceTracingDayRisk = null, - riskState = RiskState.CALCULATION_FAILED + riskState = RiskState.LOW_RISK ) ) ) @@ -303,12 +303,12 @@ class BaseRiskLevelStorageTest : BaseTest() { PtRiskLevelResult( calculatedAt = calculatedAt, presenceTracingDayRisk = null, - riskState = RiskState.CALCULATION_FAILED + riskState = RiskState.INCREASED_RISK ), PtRiskLevelResult( calculatedAt = calculatedAt.minus(400L), presenceTracingDayRisk = null, - riskState = RiskState.LOW_RISK + riskState = RiskState.CALCULATION_FAILED ) ) ) @@ -319,7 +319,7 @@ class BaseRiskLevelStorageTest : BaseTest() { riskLevelResults.size shouldBe 2 riskLevelResults[0].calculatedAt shouldBe ewCalculatedAt - riskLevelResults[0].riskState shouldBe RiskState.LOW_RISK + riskLevelResults[0].riskState shouldBe RiskState.INCREASED_RISK riskLevelResults[1].calculatedAt shouldBe ewCalculatedAt.minus(200L) riskLevelResults[1].riskState shouldBe RiskState.INCREASED_RISK diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt index a1099568a..9e1122826 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt @@ -17,7 +17,11 @@ class CombineRiskTest { @Test fun `combineRisk works`() { - val ptRisk = PresenceTracingDayRisk( + val ptRisk0 = PresenceTracingDayRisk( + localDateUtc = LocalDate(2021, 3, 19), + riskState = RiskState.LOW_RISK + ) + val ptRisk1 = PresenceTracingDayRisk( localDateUtc = LocalDate(2021, 3, 20), riskState = RiskState.INCREASED_RISK ) @@ -29,50 +33,81 @@ class CombineRiskTest { localDateUtc = LocalDate(2021, 3, 22), riskState = RiskState.CALCULATION_FAILED ) - val ewRisk = ExposureWindowDayRisk( - dateMillisSinceEpoch = Instant.parse("2021-03-22T14:00:00.000Z").millis, + val ptRisk4 = PresenceTracingDayRisk( + localDateUtc = LocalDate(2021, 3, 23), + riskState = RiskState.LOW_RISK + ) + val ptRisk5 = PresenceTracingDayRisk( + localDateUtc = LocalDate(2021, 3, 24), + riskState = RiskState.INCREASED_RISK + ) + + val ewRisk0 = ExposureWindowDayRisk( + dateMillisSinceEpoch = Instant.parse("2021-03-24T14:00:00.000Z").millis, + riskLevel = RiskLevel.HIGH, + 0, + 0 + ) + val ewRisk1 = ExposureWindowDayRisk( + dateMillisSinceEpoch = Instant.parse("2021-03-23T14:00:00.000Z").millis, riskLevel = RiskLevel.HIGH, 0, 0 ) val ewRisk2 = ExposureWindowDayRisk( + dateMillisSinceEpoch = Instant.parse("2021-03-22T14:00:00.000Z").millis, + riskLevel = RiskLevel.HIGH, + 0, + 0 + ) + val ewRisk3 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-19T14:00:00.000Z").millis, riskLevel = RiskLevel.LOW, 0, 0 ) - val ewRisk3 = ExposureWindowDayRisk( + val ewRisk4 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-20T14:00:00.000Z").millis, riskLevel = RiskLevel.UNSPECIFIED, 0, 0 ) - val ewRisk4 = ExposureWindowDayRisk( + val ewRisk5 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-15T14:00:00.000Z").millis, riskLevel = RiskLevel.UNSPECIFIED, 0, 0 ) - val ptDayRiskList: List<PresenceTracingDayRisk> = listOf(ptRisk, ptRisk2, ptRisk3) - val ewDayRiskList: List<ExposureWindowDayRisk> = listOf(ewRisk, ewRisk2, ewRisk3, ewRisk4) + val ptDayRiskList: List<PresenceTracingDayRisk> = listOf(ptRisk0, ptRisk1, ptRisk2, ptRisk3, ptRisk4, ptRisk5) + val ewDayRiskList: List<ExposureWindowDayRisk> = listOf(ewRisk0, ewRisk1, ewRisk2, ewRisk3, ewRisk4, ewRisk5) val result = combineRisk(ptDayRiskList, ewDayRiskList) - result.size shouldBe 5 - result.find { + result.size shouldBe 7 + + result.single { it.localDate == LocalDate(2021, 3, 15) - }!!.riskState shouldBe RiskState.CALCULATION_FAILED - result.find { + }.riskState shouldBe RiskState.CALCULATION_FAILED + result.single { it.localDate == LocalDate(2021, 3, 19) - }!!.riskState shouldBe RiskState.LOW_RISK - result.find { + }.riskState shouldBe RiskState.LOW_RISK + result.single { it.localDate == LocalDate(2021, 3, 20) - }!!.riskState shouldBe RiskState.INCREASED_RISK - result.find { + }.riskState shouldBe RiskState.CALCULATION_FAILED + result.single { it.localDate == LocalDate(2021, 3, 21) - }!!.riskState shouldBe RiskState.LOW_RISK - result.find { + }.riskState shouldBe RiskState.LOW_RISK + result.single { + it.localDate == LocalDate(2021, 3, 22) + }.riskState shouldBe RiskState.CALCULATION_FAILED + result.single { it.localDate == LocalDate(2021, 3, 22) - }!!.riskState shouldBe RiskState.INCREASED_RISK + }.riskState shouldBe RiskState.CALCULATION_FAILED + result.single { + it.localDate == LocalDate(2021, 3, 23) + }.riskState shouldBe RiskState.INCREASED_RISK + result.single { + it.localDate == LocalDate(2021, 3, 24) + }.riskState shouldBe RiskState.INCREASED_RISK } @Test @@ -85,7 +120,7 @@ class CombineRiskTest { ) val ptResult2 = PtRiskLevelResult( calculatedAt = startInstant.plus(3000L), - riskState = RiskState.CALCULATION_FAILED + riskState = RiskState.LOW_RISK ) val ptResult3 = PtRiskLevelResult( calculatedAt = startInstant.plus(6000L), @@ -99,7 +134,7 @@ class CombineRiskTest { val ptResults = listOf(ptResult, ptResult2, ptResult4, ptResult3) val ewResult = createEwRiskLevelResult( calculatedAt = startInstant.plus(2000L), - riskState = RiskState.CALCULATION_FAILED + riskState = RiskState.LOW_RISK ) val ewResult2 = createEwRiskLevelResult( calculatedAt = startInstant.plus(4000L), @@ -126,11 +161,11 @@ class CombineRiskTest { result[3].calculatedAt shouldBe startInstant.plus(5000L) result[4].riskState shouldBe RiskState.INCREASED_RISK result[4].calculatedAt shouldBe startInstant.plus(4000L) - result[5].riskState shouldBe RiskState.CALCULATION_FAILED + result[5].riskState shouldBe RiskState.LOW_RISK result[5].calculatedAt shouldBe startInstant.plus(3000L) result[6].riskState shouldBe RiskState.LOW_RISK result[6].calculatedAt shouldBe startInstant.plus(2000L) - result[7].riskState shouldBe RiskState.LOW_RISK + result[7].riskState shouldBe RiskState.CALCULATION_FAILED result[7].calculatedAt shouldBe startInstant.plus(1000L) } @@ -138,11 +173,11 @@ class CombineRiskTest { fun `max RiskState works`() { combine(RiskState.INCREASED_RISK, RiskState.INCREASED_RISK) shouldBe RiskState.INCREASED_RISK combine(RiskState.INCREASED_RISK, RiskState.LOW_RISK) shouldBe RiskState.INCREASED_RISK - combine(RiskState.INCREASED_RISK, RiskState.CALCULATION_FAILED) shouldBe RiskState.INCREASED_RISK + combine(RiskState.INCREASED_RISK, RiskState.CALCULATION_FAILED) shouldBe RiskState.CALCULATION_FAILED combine(RiskState.LOW_RISK, RiskState.INCREASED_RISK) shouldBe RiskState.INCREASED_RISK - combine(RiskState.CALCULATION_FAILED, RiskState.INCREASED_RISK) shouldBe RiskState.INCREASED_RISK + combine(RiskState.CALCULATION_FAILED, RiskState.INCREASED_RISK) shouldBe RiskState.CALCULATION_FAILED combine(RiskState.LOW_RISK, RiskState.LOW_RISK) shouldBe RiskState.LOW_RISK - combine(RiskState.CALCULATION_FAILED, RiskState.LOW_RISK) shouldBe RiskState.LOW_RISK + combine(RiskState.CALCULATION_FAILED, RiskState.LOW_RISK) shouldBe RiskState.CALCULATION_FAILED combine(RiskState.CALCULATION_FAILED, RiskState.CALCULATION_FAILED) shouldBe RiskState.CALCULATION_FAILED } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt index daf12a721..1be311fd3 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt @@ -87,7 +87,7 @@ class TracingDetailsItemProviderTest : BaseTest() { exposureWindows = listOf(exposureWindow) ) - every { combinedResult.ewRiskLevelResult } returns ewRiskLevelTaskResult + every { combinedResult.matchedRiskCount } returns matchedKeyCount val lastCombined = LastCombinedRiskResults( lastCalculated = combinedResult, -- GitLab