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

Commit

Permalink
Removed traces of grey risk card (EXPOSUREAPP-3051) (#1699)
Browse files Browse the repository at this point in the history
* Removed traces of grey risk card.

* Removed UNKNOWN_RISK_INITIAL constant.

* Removed unused method.

* Removed old comments.

* Introduce risklevel calculation failure state to get consistent card states.

* Cleaned up the risk card states.
Refactored "RiskLevel" to be calculated based on either result or failure reasons.
Added the new "no internet" error card.

* Updated color states.

* Fixed card color issues.

* Fixed detekt issues.

* Remove test for deleted file.

* Fix test regressions.

* Fixed tests and LINTs.

Co-authored-by: harambasicluka <[email protected]>
Co-authored-by: Matthias Urhahn <[email protected]>
  • Loading branch information
3 people authored Nov 25, 2020
1 parent 2b04bee commit 86b29f7
Show file tree
Hide file tree
Showing 58 changed files with 473 additions and 965 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(

createAdditionalRiskCalcInfo(
latestCalc.calculatedAt,
riskLevelScore = latestCalc.riskLevel.raw,
riskLevelScoreLastSuccessfulCalculated = latestSuccessfulCalc.riskLevel.raw,
riskLevel = latestCalc.riskLevel,
riskLevelLastSuccessfulCalculated = latestSuccessfulCalc.riskLevel,
matchedKeyCount = latestCalc.matchedKeyCount,
daysSinceLastExposure = latestCalc.daysWithEncounters,
lastTimeDiagnosisKeysFromServerFetch = lastTimeDiagnosisKeysFromServerFetch
Expand All @@ -158,14 +158,14 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(

private suspend fun createAdditionalRiskCalcInfo(
lastTimeRiskLevelCalculation: Instant,
riskLevelScore: Int,
riskLevelScoreLastSuccessfulCalculated: Int,
riskLevel: RiskLevel,
riskLevelLastSuccessfulCalculated: RiskLevel,
matchedKeyCount: Int,
daysSinceLastExposure: Int,
lastTimeDiagnosisKeysFromServerFetch: Date?
): String = StringBuilder()
.appendLine("Risk Level: ${RiskLevel.forValue(riskLevelScore)}")
.appendLine("Last successful Risk Level: ${RiskLevel.forValue(riskLevelScoreLastSuccessfulCalculated)}")
.appendLine("Risk Level: $riskLevel")
.appendLine("Last successful Risk Level: $riskLevelLastSuccessfulCalculated")
.appendLine("Matched key count: $matchedKeyCount")
.appendLine("Days since last Exposure: $daysSinceLastExposure days")
.appendLine("Last Time Server Fetch: ${lastTimeDiagnosisKeysFromServerFetch?.time?.let { Instant.ofEpochMilli(it) }}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
package de.rki.coronawarnapp.risk

enum class RiskLevel(val raw: Int) {
// mapped to: unknown risk - initial
// the risk score is not yet calculated
// This score is set if the application was freshly installed without running the tracing
UNKNOWN_RISK_INITIAL(RiskLevelConstants.UNKNOWN_RISK_INITIAL),

// mapped to: no calculation possible
// the ExposureNotification Framework or Bluetooth is not active
// This risk score level has the highest priority and can oversteer the other risk score levels.
Expand All @@ -31,21 +26,9 @@ enum class RiskLevel(val raw: Int) {
// and background jobs are disabled
UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL(RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL),

UNKNOWN_RISK_NO_INTERNET(RiskLevelConstants.UNKNOWN_RISK_NO_INTERNET),

// mapped to no UI state
// this should never happen
UNDETERMINED(RiskLevelConstants.UNDETERMINED);

companion object {
fun forValue(value: Int): RiskLevel {
return when (value) {
RiskLevelConstants.UNKNOWN_RISK_INITIAL -> UNKNOWN_RISK_INITIAL
RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF -> NO_CALCULATION_POSSIBLE_TRACING_OFF
RiskLevelConstants.LOW_LEVEL_RISK -> LOW_LEVEL_RISK
RiskLevelConstants.INCREASED_RISK -> INCREASED_RISK
RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS -> UNKNOWN_RISK_OUTDATED_RESULTS
RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL -> UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL
else -> UNDETERMINED
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package de.rki.coronawarnapp.risk

object RiskLevelConstants {
const val UNKNOWN_RISK_INITIAL = 0
const val NO_CALCULATION_POSSIBLE_TRACING_OFF = 1
const val LOW_LEVEL_RISK = 2
const val INCREASED_RISK = 3
const val UNKNOWN_RISK_OUTDATED_RESULTS = 4
const val UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL = 5
const val UNKNOWN_RISK_NO_INTERNET = 6
const val UNDETERMINED = 9001
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,21 @@ import de.rki.coronawarnapp.risk.result.AggregatedRiskResult
import org.joda.time.Instant

interface RiskLevelResult {
val riskLevel: RiskLevel
val calculatedAt: Instant

val riskLevel: RiskLevel
get() = when {
aggregatedRiskResult?.isIncreasedRisk() == true -> RiskLevel.INCREASED_RISK
aggregatedRiskResult?.isLowRisk() == true -> RiskLevel.LOW_LEVEL_RISK
failureReason == FailureReason.OUTDATED_RESULTS -> RiskLevel.UNKNOWN_RISK_OUTDATED_RESULTS
failureReason == FailureReason.OUTDATED_RESULTS_MANUAL -> RiskLevel.UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL
failureReason == FailureReason.TRACING_OFF -> RiskLevel.NO_CALCULATION_POSSIBLE_TRACING_OFF
failureReason == FailureReason.NO_INTERNET -> RiskLevel.UNKNOWN_RISK_NO_INTERNET
failureReason == FailureReason.UNKNOWN -> RiskLevel.UNDETERMINED
else -> RiskLevel.UNDETERMINED
}

val failureReason: FailureReason?
val aggregatedRiskResult: AggregatedRiskResult?

/**
Expand All @@ -16,7 +28,7 @@ interface RiskLevelResult {
val exposureWindows: List<ExposureWindow>?

val wasSuccessfullyCalculated: Boolean
get() = !UNSUCCESSFUL_RISK_LEVELS.contains(riskLevel)
get() = aggregatedRiskResult != null

val isIncreasedRisk: Boolean
get() = aggregatedRiskResult?.isIncreasedRisk() ?: false
Expand All @@ -42,6 +54,14 @@ interface RiskLevelResult {
aggregatedRiskResult?.mostRecentDateWithLowRisk
}

enum class FailureReason(val failureCode: String) {
UNKNOWN("unknown"),
TRACING_OFF("tracingOff"),
NO_INTERNET("noInternet"),
OUTDATED_RESULTS("outDatedResults"),
OUTDATED_RESULTS_MANUAL("outDatedResults.manual")
}

companion object {
private val UNSUCCESSFUL_RISK_LEVELS = arrayOf(
RiskLevel.UNDETERMINED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@ import de.rki.coronawarnapp.exception.ExceptionCategory
import de.rki.coronawarnapp.exception.RiskLevelCalculationException
import de.rki.coronawarnapp.exception.reporting.report
import de.rki.coronawarnapp.nearby.ENFClient
import de.rki.coronawarnapp.risk.RiskLevel.INCREASED_RISK
import de.rki.coronawarnapp.risk.RiskLevel.LOW_LEVEL_RISK
import de.rki.coronawarnapp.risk.RiskLevel.NO_CALCULATION_POSSIBLE_TRACING_OFF
import de.rki.coronawarnapp.risk.RiskLevel.UNDETERMINED
import de.rki.coronawarnapp.risk.RiskLevel.UNKNOWN_RISK_INITIAL
import de.rki.coronawarnapp.risk.RiskLevel.UNKNOWN_RISK_OUTDATED_RESULTS
import de.rki.coronawarnapp.risk.RiskLevel.UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL
import de.rki.coronawarnapp.risk.RiskLevelResult.FailureReason
import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
import de.rki.coronawarnapp.task.Task
import de.rki.coronawarnapp.task.TaskCancellationException
Expand Down Expand Up @@ -52,76 +47,60 @@ class RiskLevelTask @Inject constructor(
private var isCanceled = false

@Suppress("LongMethod")
override suspend fun run(arguments: Task.Arguments): RiskLevelTaskResult {
try {
Timber.d("Running with arguments=%s", arguments)
if (!isNetworkEnabled(context)) {
return RiskLevelTaskResult(
riskLevel = UNDETERMINED,
calculatedAt = timeStamper.nowUTC
)
}
override suspend fun run(arguments: Task.Arguments): RiskLevelTaskResult = try {
Timber.d("Running with arguments=%s", arguments)

if (!enfClient.isTracingEnabled.first()) {
return RiskLevelTaskResult(
riskLevel = NO_CALCULATION_POSSIBLE_TRACING_OFF,
calculatedAt = timeStamper.nowUTC
)
}
val configData: ConfigData = appConfigProvider.getAppConfig()

val configData: ConfigData = appConfigProvider.getAppConfig()
determineRiskLevelResult(configData).also {
Timber.i("Risklevel determined: %s", it)

return kotlin.run evaluation@{
if (calculationNotPossibleBecauseOfNoKeys()) {
return@evaluation RiskLevelTaskResult(
riskLevel = UNKNOWN_RISK_INITIAL,
calculatedAt = timeStamper.nowUTC
)
}
checkCancel()

if (calculationNotPossibleBecauseOfOutdatedResults()) {
return@evaluation RiskLevelTaskResult(
riskLevel = when (backgroundJobsEnabled()) {
true -> UNKNOWN_RISK_OUTDATED_RESULTS
false -> UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL
},
calculatedAt = timeStamper.nowUTC
)
}
checkCancel()

val risklevelResult = calculateRiskLevel(configData)
if (risklevelResult.isIncreasedRisk) {
return@evaluation risklevelResult
}
checkCancel()
Timber.tag(TAG).d("storeTaskResult(...)")
riskLevelStorage.storeResult(it)

if (!isActiveTracingTimeAboveThreshold()) {
return@evaluation RiskLevelTaskResult(
riskLevel = UNKNOWN_RISK_INITIAL,
calculatedAt = timeStamper.nowUTC
)
}
checkCancel()
riskLevelSettings.lastUsedConfigIdentifier = configData.identifier
}
} catch (error: Exception) {
Timber.tag(TAG).e(error)
error.report(ExceptionCategory.EXPOSURENOTIFICATION)
throw error
} finally {
Timber.i("Finished (isCanceled=$isCanceled).")
internalProgress.close()
}

return@evaluation risklevelResult
}.also {
checkCancel()
Timber.i("Evaluation finished with %s", it)
private suspend fun determineRiskLevelResult(configData: ConfigData): RiskLevelTaskResult {
if (!isNetworkEnabled(context)) {
Timber.i("Risk not calculated, internet unavailable.")
return RiskLevelTaskResult(
calculatedAt = timeStamper.nowUTC,
failureReason = FailureReason.NO_INTERNET
)
}

Timber.tag(TAG).d("storeTaskResult(...)")
riskLevelStorage.storeResult(it)
if (!enfClient.isTracingEnabled.first()) {
Timber.i("Risk not calculated, tracing is disabled.")
return RiskLevelTaskResult(
calculatedAt = timeStamper.nowUTC,
failureReason = FailureReason.TRACING_OFF
)
}

riskLevelSettings.lastUsedConfigIdentifier = configData.identifier
}
} catch (error: Exception) {
Timber.tag(TAG).e(error)
error.report(ExceptionCategory.EXPOSURENOTIFICATION)
throw error
} finally {
Timber.i("Finished (isCanceled=$isCanceled).")
internalProgress.close()
if (calculationNotPossibleBecauseOfOutdatedResults()) {
Timber.i("Risk not calculated, results are outdated.")
return RiskLevelTaskResult(
calculatedAt = timeStamper.nowUTC,
failureReason = when (backgroundJobsEnabled()) {
true -> FailureReason.OUTDATED_RESULTS
false -> FailureReason.OUTDATED_RESULTS_MANUAL
}
)
}
checkCancel()

return calculateRiskLevel(configData)
}

private fun calculationNotPossibleBecauseOfOutdatedResults(): Boolean {
Expand All @@ -144,17 +123,6 @@ class RiskLevelTask @Inject constructor(
}
}

private fun calculationNotPossibleBecauseOfNoKeys(): Boolean {
Timber.tag(TAG).d("Evaluating calculationNotPossibleBecauseOfNoKeys()")
return (TimeVariables.getLastTimeDiagnosisKeysFromServerFetch() == null).also {
if (it) {
Timber.tag(TAG).v("No last time diagnosis keys from server fetch timestamp was found")
} else {
Timber.tag(TAG).d("Diagnosis keys from server are available, continuing evaluation.")
}
}
}

private fun isActiveTracingTimeAboveThreshold(): Boolean {
Timber.tag(TAG).d("Evaluating isActiveTracingTimeAboveThreshold()")

Expand All @@ -171,30 +139,25 @@ class RiskLevelTask @Inject constructor(
}

private suspend fun calculateRiskLevel(configData: ExposureWindowRiskCalculationConfig): RiskLevelTaskResult {
Timber.tag(TAG).d("Evaluating isIncreasedRisk(...)")
Timber.tag(TAG).d("Calculating risklevel")
val exposureWindows = enfClient.exposureWindows()

return riskLevels.determineRisk(configData, exposureWindows).let {
Timber.tag(TAG).d("Evaluated increased risk: %s", it)
Timber.tag(TAG).d("Risklevel calculated: %s", it)
if (it.isIncreasedRisk()) {
Timber.tag(TAG).i("Risk is increased!")
} else {
Timber.tag(TAG).d("Risk is not increased, continuing evaluating.")
}

RiskLevelTaskResult(
riskLevel = if (it.isIncreasedRisk()) INCREASED_RISK else LOW_LEVEL_RISK,
calculatedAt = timeStamper.nowUTC,
aggregatedRiskResult = it,
exposureWindows = exposureWindows,
calculatedAt = timeStamper.nowUTC
exposureWindows = exposureWindows
)
}
}

private fun checkCancel() {
if (isCanceled) throw TaskCancellationException()
}

private suspend fun backgroundJobsEnabled() =
backgroundModeStatus.isAutoModeEnabled.first().also {
if (it) {
Expand All @@ -206,6 +169,10 @@ class RiskLevelTask @Inject constructor(
}
}

private fun checkCancel() {
if (isCanceled) throw TaskCancellationException()
}

override suspend fun cancel() {
Timber.w("cancel() called.")
isCanceled = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,30 @@ import de.rki.coronawarnapp.task.Task
import org.joda.time.Instant

data class RiskLevelTaskResult(
override val riskLevel: RiskLevel,
override val calculatedAt: Instant,
override val aggregatedRiskResult: AggregatedRiskResult? = null,
override val exposureWindows: List<ExposureWindow>? = null
) : Task.Result, RiskLevelResult
override val failureReason: RiskLevelResult.FailureReason?,
override val aggregatedRiskResult: AggregatedRiskResult?,
override val exposureWindows: List<ExposureWindow>?
) : Task.Result, RiskLevelResult {

constructor(
calculatedAt: Instant,
aggregatedRiskResult: AggregatedRiskResult,
exposureWindows: List<ExposureWindow>?
) : this(
calculatedAt = calculatedAt,
aggregatedRiskResult = aggregatedRiskResult,
exposureWindows = exposureWindows,
failureReason = null
)

constructor(
calculatedAt: Instant,
failureReason: RiskLevelResult.FailureReason
) : this(
calculatedAt = calculatedAt,
failureReason = failureReason,
aggregatedRiskResult = null,
exposureWindows = null
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ abstract class BaseRiskLevelStorage constructor(
val storedResultId = try {
val startTime = System.currentTimeMillis()

require(result.aggregatedRiskResult == null || result.failureReason == null) {
"A result needs to have either an aggregatedRiskResult or a failureReason, not both!"
}

val resultToPersist = result.toPersistedRiskResult()
riskResultsTables.insertEntry(resultToPersist).also {
Timber.d("Storing RiskLevelResult took %dms.", (System.currentTimeMillis() - startTime))
Expand Down Expand Up @@ -92,6 +96,7 @@ abstract class BaseRiskLevelStorage constructor(
override val riskLevel: RiskLevel = RiskLevel.LOW_LEVEL_RISK
override val calculatedAt: Instant = Instant.EPOCH
override val aggregatedRiskResult: AggregatedRiskResult? = null
override val failureReason: RiskLevelResult.FailureReason? = null
override val exposureWindows: List<ExposureWindow>? = null
override val matchedKeyCount: Int = 0
override val daysWithEncounters: Int = 0
Expand Down
Loading

0 comments on commit 86b29f7

Please sign in to comment.