From c41b781fb2a98ab2617e100e5d804c100792ada6 Mon Sep 17 00:00:00 2001 From: BMItter Date: Mon, 2 Nov 2020 20:44:39 +0100 Subject: [PATCH 01/47] temporary Params from current ENFv2 Draft - neccassary to work, but will be overwritten later --- .../internal/v2/app_config_android.proto | 50 +++++++++++++++ .../main/proto/internal/v2/app_features.proto | 11 ++++ .../v2/exposure_detection_parameters.proto | 14 +++++ .../internal/v2/key_download_parameters.proto | 31 +++++++++ .../v2/risk_calculation_parameters.proto | 63 +++++++++++++++++++ .../proto/internal/v2/semantic_version.proto | 8 +++ 6 files changed, 177 insertions(+) create mode 100644 Server-Protocol-Buffer/src/main/proto/internal/v2/app_config_android.proto create mode 100644 Server-Protocol-Buffer/src/main/proto/internal/v2/app_features.proto create mode 100644 Server-Protocol-Buffer/src/main/proto/internal/v2/exposure_detection_parameters.proto create mode 100644 Server-Protocol-Buffer/src/main/proto/internal/v2/key_download_parameters.proto create mode 100644 Server-Protocol-Buffer/src/main/proto/internal/v2/risk_calculation_parameters.proto create mode 100644 Server-Protocol-Buffer/src/main/proto/internal/v2/semantic_version.proto diff --git a/Server-Protocol-Buffer/src/main/proto/internal/v2/app_config_android.proto b/Server-Protocol-Buffer/src/main/proto/internal/v2/app_config_android.proto new file mode 100644 index 00000000000..6f6d21c22cb --- /dev/null +++ b/Server-Protocol-Buffer/src/main/proto/internal/v2/app_config_android.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; +package de.rki.coronawarnapp.server.protocols.internal.v2; +import "internal/v2/app_features.proto"; +import "internal/v2/risk_calculation_parameters.proto"; +import "internal/v2/key_download_parameters.proto"; +import "internal/v2/exposure_detection_parameters.proto"; + +message ApplicationConfigurationAndroid { + + // Android apps are versioned by Version Code and not by Semantic Versioning + int64 minVersionCode = 1; + int64 latestVersionCode = 2; + + AppFeatures appFeatures = 3; + + repeated string supportedCountries = 4; + + KeyDownloadParametersAndroid keyDownloadParameters = 5; + + ExposureDetectionParametersAndroid exposureDetectionParameters = 6; + + RiskCalculationParameters riskCalculationParameters = 7; + + DiagnosisKeysDataMapping diagnosisKeysDataMapping = 8; + DailySummariesConfig dailySummariesConfig = 9; +} + +message DiagnosisKeysDataMapping { + + map daysSinceOnsetToInfectiousness = 1; + + int32 infectiousnessWhenDaysSinceOnsetMissing = 2; + + int32 reportTypeWhenMissing = 3; +} + +message DailySummariesConfig { + + repeated int32 attenuationBucketThresholdDb = 1; + + repeated double attenuationBucketWeights = 2; + + int32 daysSinceExposureThreshold = 3; + + map infectiousnessWeights = 4; + + double minimumWindowScore = 5; + + map reportTypeWeights = 6; +} \ No newline at end of file diff --git a/Server-Protocol-Buffer/src/main/proto/internal/v2/app_features.proto b/Server-Protocol-Buffer/src/main/proto/internal/v2/app_features.proto new file mode 100644 index 00000000000..f53be3812f3 --- /dev/null +++ b/Server-Protocol-Buffer/src/main/proto/internal/v2/app_features.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package de.rki.coronawarnapp.server.protocols.internal.v2; + +message AppFeatures { + repeated AppFeature appFeatures = 1; +} + +message AppFeature { + string label = 1; + int32 value = 2; +} \ No newline at end of file diff --git a/Server-Protocol-Buffer/src/main/proto/internal/v2/exposure_detection_parameters.proto b/Server-Protocol-Buffer/src/main/proto/internal/v2/exposure_detection_parameters.proto new file mode 100644 index 00000000000..754bd769abd --- /dev/null +++ b/Server-Protocol-Buffer/src/main/proto/internal/v2/exposure_detection_parameters.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package de.rki.coronawarnapp.server.protocols.internal.v2; + +message ExpsureDetectionParametersIOS { + + int32 maxExposureDetectionsPerInterval = 1; +} + +message ExposureDetectionParametersAndroid { + + int32 maxExposureDetectionsPerInterval = 1; + + int32 overallTimeoutInSeconds = 2; +} \ No newline at end of file diff --git a/Server-Protocol-Buffer/src/main/proto/internal/v2/key_download_parameters.proto b/Server-Protocol-Buffer/src/main/proto/internal/v2/key_download_parameters.proto new file mode 100644 index 00000000000..4cf97b97758 --- /dev/null +++ b/Server-Protocol-Buffer/src/main/proto/internal/v2/key_download_parameters.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; +package de.rki.coronawarnapp.server.protocols.internal.v2; + +message KeyDownloadParametersIOS { + + repeated DayPackageMetadata cachedDayPackagesToUpdateOnETagMismatch = 1; + repeated HourPackageMetadata cachedHourPackagesToUpdateOnETagMismatch = 2; +} + +message KeyDownloadParametersAndroid { + + repeated DayPackageMetadata cachedDayPackagesToUpdateOnETagMismatch = 1; + repeated HourPackageMetadata cachedHourPackagesToUpdateOnETagMismatch = 2; + + int32 downloadTimeoutInSeconds = 3; + + int32 overallTimeoutInSeconds = 4; +} + +message DayPackageMetadata { + string region = 1; + string date = 2; + string etag = 3; +} + +message HourPackageMetadata { + string region = 1; + string date = 2; + int32 hour = 3; + string etag = 4; +} \ No newline at end of file diff --git a/Server-Protocol-Buffer/src/main/proto/internal/v2/risk_calculation_parameters.proto b/Server-Protocol-Buffer/src/main/proto/internal/v2/risk_calculation_parameters.proto new file mode 100644 index 00000000000..2618917a091 --- /dev/null +++ b/Server-Protocol-Buffer/src/main/proto/internal/v2/risk_calculation_parameters.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; +package de.rki.coronawarnapp.server.protocols.internal.v2; + +message RiskCalculationParameters { + + repeated MinutesAtAttenuationFilter minutesAtAttenuationFilters = 1; + + repeated TrlFilter trlFilters = 2; + + repeated MinutesAtAttenuationWeight minutesAtAttenuationWeights = 3; + + repeated NormalizedTimeToRiskLevelMapping normalizedTimePerEWToRiskLevelMapping = 4; + + repeated NormalizedTimeToRiskLevelMapping normalizedTimePerDayToRiskLevelMapping = 5; + + TransmissionRiskLevelEncoding trlEncoding = 6; + + double transmissionRiskLevelMultiplier = 7; +} + +message Range { + // The lower limit of the range + double min = 1; + // True if `min` is not part of the range, false otherwise + bool minExclusive = 2; + // The upper limit of the range + double max = 3; + // True if `max` is not part of the range, false otherwise + bool maxExclusive = 4; +} + +message MinutesAtAttenuationFilter { + Range attenuationRange = 1; + Range dropIfMinutesInRange = 2; +} + +message TrlFilter { + Range dropIfTrlInRange = 1; +} + +message MinutesAtAttenuationWeight { + Range attenuationRange = 1; + double weight = 2; +} + +message NormalizedTimeToRiskLevelMapping { + Range normalizedTimeRange = 1; + enum RiskLevel { + UNSPECIFIED = 0; + LOW = 1; + HIGH = 2; + } + RiskLevel riskLevel = 2; +} + +message TransmissionRiskLevelEncoding { + int32 infectiousnessOffsetStandard = 1; + int32 infectiousnessOffsetHigh = 2; + int32 reportTypeOffsetRecursive = 3; + int32 reportTypeOffsetSelfReport = 4; + int32 reportTypeOffsetConfirmedClinicalDiagnosis = 5; + int32 reportTypeOffsetConfirmedTest = 6; +} \ No newline at end of file diff --git a/Server-Protocol-Buffer/src/main/proto/internal/v2/semantic_version.proto b/Server-Protocol-Buffer/src/main/proto/internal/v2/semantic_version.proto new file mode 100644 index 00000000000..a5950c84435 --- /dev/null +++ b/Server-Protocol-Buffer/src/main/proto/internal/v2/semantic_version.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; +package de.rki.coronawarnapp.server.protocols.internal.v2; + +message SemanticVersion { + uint32 major = 1; + uint32 minor = 2; + uint32 patch = 3; +} \ No newline at end of file From d2f89e74997fffc9d0d47a96ad5ff5ae4513c1b0 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Tue, 10 Nov 2020 15:45:30 +0100 Subject: [PATCH 02/47] Determine Risk Level for Exposure Windows & Aggregation (EXPOSUREAPP-3537, EXPOSUREAPP-3518) (#1546) * Split and hide the protobuf config behind interfaces with individual mappers responsible for creating the desired formats. * Merge branch 'release/1.7.x' into feature/3455-more-frequent-riskscore-updates-configs # Conflicts: # Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt # Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RiskLevelTransaction.kt # Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RiskLevelTransactionTest.kt * Make the AppConfig observable. Provide the server time offset. Offer a lastUpdatedAt timestamp. Add an app config specific test screen. Clean up test screens a bit and move debug options out of API test options. * Fix test regression due to refactoring (moved code around). * Store the server timestamp and offset at retrieval. Switch to config storage via json to be able to store additional meta data fields (i.e. time). * KLint and Me have a hate relationship based on both mutual admiration. * Fix time offset parsing being locale dependent. * Fix broken unit tests. * Improve offset accuracy, move before unzipping. * Fix overly long livedata subscription to results (viewmodel scope vs observer scope) * Add mapping for the new protobuf configs + tests. * For cached (retrofit) response, we need to check the cacheResponse and its timestamps to determine an accurate time offset. * Exposure a boolean property to tell us when a fallback config is being used. * Hide the observable flow behind a method that can automatically triggers refreshes. * Use a common mapper interface. * set old risklevelcalculation deprecated * Created skeleton for new risk calculation and aggregation * Implementing steps to aggregate results form exposure windows - wip * Address PR comments and KLints. * Fix refactoring regression. * ktlint * Added ExposureWindowRiskLevelConfig and ExposureWindowRiskLevelConfigMapper for new config api (not yet introduced) Signed-off-by: Kolya Opahle * Added first Implementation of exposure window based calculateRisk function Signed-off-by: Kolya Opahle * Added generics to Range.inRange Signed-off-by: Kolya Opahle * Added Ugly Hack to RiskLevelTransaction to allow for compilation during testing Signed-off-by: Kolya Opahle * Linting and injecting RiskLevelCalculation into TestRiskLevelCalculationFragmentCWAViewModel, currently wont build because ExposureWindowRiskLevelConfig has no Provider Signed-off-by: Kolya Opahle * Linting extravaganza Signed-off-by: Kolya Opahle * Lint Wars Episode VI: Return of the trailing Comma * Improve config unzipping code. * Add flag to forward exception thrown during HotDataFlow.kt initialization. * Don't specify a default context via singleton. * Move download and fallback logic into it's own class just responsible for sourcing the config: "AppConfigSource". "AppConfigProvider" is now only responsible for making it available. * Simplify current concepts for making the app config observable until we have a default configuration. * Improve app config test screen, delete options, better feedback. Show toast instead of crash on errors. * Fixed GSON serialization not encoding/decoding the byte array correctly. Added specific type adapters for instant and duration to get cleaner json. * Remove type adapters from base gson due to conflict with CalculationTrackerStorage. * We want to default to forced serialization of instant by our converters, instead of using the default serialization which will differ between Java8.Instant and JodaTime.Instant, to prevent future headaches there, register explicit converters by default, and overwrite them if necessary (currently only needed for CalculationTrackerStorage.kt). * Improve AppConfigServer code readability by moving code into extensions. * Fix merge conflicts * Added missing import to WorkerBinderTest * fixed unit tests * Removed auto formatting on unrelated files (revert + cherry pick in other commit) Signed-off-by: Kolya Opahle * Implementing steps to aggregate results form exposure windows * Renamed ExposureWindowRiskLevelConfig to ExposureWindowRiskCalculationConfig * adjusted & refactored Windows aggregation * removed example Values * satisfy lint * make Aggregation work with Instant now * Use long while calculation * Added normalizedTimePerDayToRiskLevelMappingList to AppConfig * normalizedTimePerDayToRiskLevelMappingList from AppConfig * satisfy lint * Get AppConfig on init and listen for updates * exposureData to aggregatedRiskPerDateResult * Corrected name in ConfigParserTest * use instant for specific aggregation logs * satisfy CI * satisfy detekt * exposure history exception & log adjustment * Fixed unittests for new config parser and risk levels Signed-off-by: Kolya Opahle * Added some logging to the calculateRisk function and removed the suspend qualifiers as AppConfig is fetched during init Signed-off-by: Kolya Opahle Co-authored-by: Matthias Urhahn Co-authored-by: BMItter Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> --- .../appconfig/AppConfigModule.kt | 18 +- .../ExposureWindowRiskCalculationConfig.kt | 20 ++ .../appconfig/mapping/ConfigMapping.kt | 4 +- .../appconfig/mapping/ConfigParser.kt | 13 +- .../appconfig/mapping/DefaultConfigMapping.kt | 7 +- ...posureWindowRiskCalculationConfigMapper.kt | 44 +++ .../coronawarnapp/risk/DefaultRiskLevels.kt | 323 +++++++++++++++++- .../rki/coronawarnapp/risk/RiskLevelTask.kt | 22 +- .../de/rki/coronawarnapp/risk/RiskLevels.kt | 12 + .../result/AggregatedRiskPerDateResult.kt | 10 + .../risk/result/AggregatedRiskResult.kt | 12 + .../coronawarnapp/risk/result/RiskResult.kt | 10 + .../appconfig/mapping/ConfigParserTest.kt | 7 +- .../rki/coronawarnapp/risk/RiskLevelsTest.kt | 7 + 14 files changed, 486 insertions(+), 23 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResult.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/RiskResult.kt diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt index 82936e52b38..87d675328c7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.appconfig.download.AppConfigHttpCache import de.rki.coronawarnapp.appconfig.mapping.CWAConfigMapper import de.rki.coronawarnapp.appconfig.mapping.DownloadConfigMapper import de.rki.coronawarnapp.appconfig.mapping.ExposureDetectionConfigMapper +import de.rki.coronawarnapp.appconfig.mapping.ExposureWindowRiskCalculationConfigMapper import de.rki.coronawarnapp.appconfig.mapping.RiskCalculationConfigMapper import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient import de.rki.coronawarnapp.environment.download.DownloadCDNServerUrl @@ -61,17 +62,24 @@ class AppConfigModule { } @Provides - fun cwaMapper(mapper: CWAConfigMapper): CWAConfig.Mapper = mapper + fun cwaMapper(mapper: CWAConfigMapper): + CWAConfig.Mapper = mapper @Provides - fun downloadMapper(mapper: DownloadConfigMapper): KeyDownloadConfig.Mapper = mapper + fun downloadMapper(mapper: DownloadConfigMapper): + KeyDownloadConfig.Mapper = mapper @Provides - fun exposurMapper(mapper: ExposureDetectionConfigMapper): ExposureDetectionConfig.Mapper = - mapper + fun exposurMapper(mapper: ExposureDetectionConfigMapper): + ExposureDetectionConfig.Mapper = mapper @Provides - fun riskMapper(mapper: RiskCalculationConfigMapper): RiskCalculationConfig.Mapper = mapper + fun riskMapper(mapper: RiskCalculationConfigMapper): + RiskCalculationConfig.Mapper = mapper + + @Provides + fun windowRiskMapper(mapper: ExposureWindowRiskCalculationConfigMapper): + ExposureWindowRiskCalculationConfig.Mapper = mapper companion object { private val HTTP_TIMEOUT_APPCONFIG = Duration.standardSeconds(10) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt new file mode 100644 index 00000000000..80eadd589d6 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt @@ -0,0 +1,20 @@ +package de.rki.coronawarnapp.appconfig + +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass + +interface ExposureWindowRiskCalculationConfig { + val minutesAtAttenuationFilters: List + val minutesAtAttenuationWeights: List + val transmissionRiskLevelEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding + val transmissionRiskLevelFilters: List + val transmissionRiskLevelMultiplier: Double + val normalizedTimePerExposureWindowToRiskLevelMapping: + List + val normalizedTimePerDayToRiskLevelMappingList: + List + + interface Mapper { + fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): ExposureWindowRiskCalculationConfig + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt index 9858ec812bf..1e9ccb2d404 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.appconfig.mapping import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig +import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.KeyDownloadConfig import de.rki.coronawarnapp.appconfig.RiskCalculationConfig import de.rki.coronawarnapp.server.protocols.internal.AppConfig @@ -10,7 +11,8 @@ interface ConfigMapping : CWAConfig, KeyDownloadConfig, ExposureDetectionConfig, - RiskCalculationConfig { + RiskCalculationConfig, + ExposureWindowRiskCalculationConfig { @Deprecated("Try to access a more specific config type, avoid the RAW variant.") val rawConfig: AppConfig.ApplicationConfiguration diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt index 8449b81b7cf..41988550a2c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt @@ -3,9 +3,11 @@ package de.rki.coronawarnapp.appconfig.mapping import dagger.Reusable import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig +import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.KeyDownloadConfig import de.rki.coronawarnapp.appconfig.RiskCalculationConfig import de.rki.coronawarnapp.server.protocols.internal.AppConfig +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import timber.log.Timber import javax.inject.Inject @@ -14,17 +16,24 @@ class ConfigParser @Inject constructor( private val cwaConfigMapper: CWAConfig.Mapper, private val keyDownloadConfigMapper: KeyDownloadConfig.Mapper, private val exposureDetectionConfigMapper: ExposureDetectionConfig.Mapper, - private val riskCalculationConfigMapper: RiskCalculationConfig.Mapper + private val riskCalculationConfigMapper: RiskCalculationConfig.Mapper, + private val exposureWindowRiskCalculationConfigMapper: ExposureWindowRiskCalculationConfig.Mapper ) { fun parse(configBytes: ByteArray): ConfigMapping = try { + // TODO replace with actual v2 config + val dummyConfig = AppConfigAndroid + .ApplicationConfigurationAndroid + .newBuilder() + .build() parseRawArray(configBytes).let { DefaultConfigMapping( rawConfig = it, cwaConfig = cwaConfigMapper.map(it), keyDownloadConfig = keyDownloadConfigMapper.map(it), exposureDetectionConfig = exposureDetectionConfigMapper.map(it), - riskCalculationConfig = riskCalculationConfigMapper.map(it) + riskCalculationConfig = riskCalculationConfigMapper.map(it), + exposureWindowRiskCalculationConfig = exposureWindowRiskCalculationConfigMapper.map(dummyConfig) ) } } catch (e: Exception) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt index 783385ddfdd..78bacc12c69 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.appconfig.mapping import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig +import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.KeyDownloadConfig import de.rki.coronawarnapp.appconfig.RiskCalculationConfig import de.rki.coronawarnapp.server.protocols.internal.AppConfig @@ -11,9 +12,11 @@ data class DefaultConfigMapping( val cwaConfig: CWAConfig, val keyDownloadConfig: KeyDownloadConfig, val exposureDetectionConfig: ExposureDetectionConfig, - val riskCalculationConfig: RiskCalculationConfig + val riskCalculationConfig: RiskCalculationConfig, + val exposureWindowRiskCalculationConfig: ExposureWindowRiskCalculationConfig ) : ConfigMapping, CWAConfig by cwaConfig, KeyDownloadConfig by keyDownloadConfig, ExposureDetectionConfig by exposureDetectionConfig, - RiskCalculationConfig by riskCalculationConfig + RiskCalculationConfig by riskCalculationConfig, + ExposureWindowRiskCalculationConfig by exposureWindowRiskCalculationConfig diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt new file mode 100644 index 00000000000..e64fdc2bad1 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt @@ -0,0 +1,44 @@ +package de.rki.coronawarnapp.appconfig.mapping + +import dagger.Reusable +import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass +import javax.inject.Inject + +@Reusable +class ExposureWindowRiskCalculationConfigMapper @Inject constructor() : + ExposureWindowRiskCalculationConfig.Mapper { + + override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): ExposureWindowRiskCalculationConfig { + val riskCalculationParameters = rawConfig.riskCalculationParameters + return ExposureWindowRiskCalculationContainer( + minutesAtAttenuationFilters = riskCalculationParameters + .minutesAtAttenuationFiltersList, + minutesAtAttenuationWeights = riskCalculationParameters + .minutesAtAttenuationWeightsList, + transmissionRiskLevelEncoding = riskCalculationParameters + .trlEncoding, + transmissionRiskLevelFilters = riskCalculationParameters + .trlFiltersList, + transmissionRiskLevelMultiplier = riskCalculationParameters + .transmissionRiskLevelMultiplier, + normalizedTimePerExposureWindowToRiskLevelMapping = riskCalculationParameters + .normalizedTimePerEWToRiskLevelMappingList, + normalizedTimePerDayToRiskLevelMappingList = riskCalculationParameters + .normalizedTimePerDayToRiskLevelMappingList + ) + } + + data class ExposureWindowRiskCalculationContainer( + override val minutesAtAttenuationFilters: List, + override val minutesAtAttenuationWeights: List, + override val transmissionRiskLevelEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding, + override val transmissionRiskLevelFilters: List, + override val transmissionRiskLevelMultiplier: Double, + override val normalizedTimePerExposureWindowToRiskLevelMapping: + List, + override val normalizedTimePerDayToRiskLevelMappingList: + List + ) : ExposureWindowRiskCalculationConfig +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index 4df5b3228da..e0fd8a80dae 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -1,19 +1,32 @@ package de.rki.coronawarnapp.risk +import android.text.TextUtils import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationCompat import com.google.android.gms.nearby.exposurenotification.ExposureSummary +import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import com.google.android.gms.nearby.exposurenotification.Infectiousness +import com.google.android.gms.nearby.exposurenotification.ReportType import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.appconfig.ConfigData +import de.rki.coronawarnapp.appconfig.download.ApplicationConfigurationInvalidException import de.rki.coronawarnapp.exception.RiskLevelCalculationException import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.risk.RiskLevel.UNKNOWN_RISK_INITIAL import de.rki.coronawarnapp.risk.RiskLevel.UNKNOWN_RISK_OUTDATED_RESULTS +import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.RiskResult import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass.AttenuationDuration +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.RiskLevelRepository import de.rki.coronawarnapp.util.TimeAndDateExtensions.millisecondsToHours +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -24,6 +37,17 @@ class DefaultRiskLevels @Inject constructor( private val appConfigProvider: AppConfigProvider ) : RiskLevels { + private var appConfig: ConfigData + + init { + runBlocking { + appConfig = appConfigProvider.getAppConfig() + } + + appConfigProvider.currentConfig + .onEach { if (it != null) appConfig = it } + } + override fun updateRepository(riskLevel: RiskLevel, time: Long) { val rollbackItems = mutableListOf() try { @@ -207,14 +231,311 @@ class DefaultRiskLevels @Inject constructor( ) } if (lastCalculatedScore.raw == RiskLevelConstants.INCREASED_RISK && - riskLevel.raw == RiskLevelConstants.LOW_LEVEL_RISK) { + riskLevel.raw == RiskLevelConstants.LOW_LEVEL_RISK + ) { LocalData.isUserToBeNotifiedOfLoweredRiskLevel = true } RiskLevelRepository.setRiskLevelScore(riskLevel) } + private fun dropDueToMinutesAtAttenuation( + exposureWindow: ExposureWindow, + attenuationFilters: List + ) = + attenuationFilters.any { attenuationFilter -> + // Get total seconds at attenuation in exposure window + val secondsAtAttenuation = exposureWindow.scanInstances + .filter { attenuationFilter.attenuationRange.inRange(it.typicalAttenuationDb) } + .fold(0) { acc, scanInstance -> acc + scanInstance.secondsSinceLastScan } + + val minutesAtAttenuation = secondsAtAttenuation / 60 + return attenuationFilter.dropIfMinutesInRange.inRange(minutesAtAttenuation) + } + + private fun determineTransmissionRiskLevel( + exposureWindow: ExposureWindow, + transmissionRiskLevelEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding + ): Int { + + val reportTypeOffset = when (exposureWindow.reportType) { + ReportType.RECURSIVE -> transmissionRiskLevelEncoding + .reportTypeOffsetRecursive + ReportType.SELF_REPORT -> transmissionRiskLevelEncoding + .reportTypeOffsetSelfReport + ReportType.CONFIRMED_CLINICAL_DIAGNOSIS -> transmissionRiskLevelEncoding + .reportTypeOffsetConfirmedClinicalDiagnosis + ReportType.CONFIRMED_TEST -> transmissionRiskLevelEncoding + .reportTypeOffsetConfirmedTest + else -> throw UnknownReportTypeException() + } + + val infectiousnessOffset = when (exposureWindow.infectiousness) { + Infectiousness.HIGH -> transmissionRiskLevelEncoding + .infectiousnessOffsetHigh + else -> transmissionRiskLevelEncoding + .infectiousnessOffsetStandard + } + + return reportTypeOffset + infectiousnessOffset + } + + private fun dropDueToTransmissionRiskLevel( + transmissionRiskLevel: Int, + transmissionRiskLevelFilters: List + ) = + transmissionRiskLevelFilters.any { + it.dropIfTrlInRange.inRange(transmissionRiskLevel) + } + + private fun determineWeightedSeconds( + exposureWindow: ExposureWindow, + minutesAtAttenuationWeight: List + ): Double = + exposureWindow.scanInstances.fold(.0) { seconds, scanInstance -> + val weight = + minutesAtAttenuationWeight + .filter { it.attenuationRange.inRange(scanInstance.typicalAttenuationDb) } + .map { it.weight } + .firstOrNull() ?: .0 + return seconds + scanInstance.secondsSinceLastScan * weight + } + + private fun determineRiskLevel( + normalizedTime: Double, + timeToRiskLevelMapping: List + ) = + timeToRiskLevelMapping + .filter { it.normalizedTimeRange.inRange(normalizedTime) } + .map { it.riskLevel } + .firstOrNull() + + override fun calculateRisk( + exposureWindow: ExposureWindow + ): RiskResult? { + if (dropDueToMinutesAtAttenuation(exposureWindow, appConfig.minutesAtAttenuationFilters)) { + Timber.d( + "%s dropped due to minutes at attenuation filter", + exposureWindow + ) + return null + } + + val transmissionRiskLevel = determineTransmissionRiskLevel( + exposureWindow, + appConfig.transmissionRiskLevelEncoding + ) + + if (dropDueToTransmissionRiskLevel(transmissionRiskLevel, appConfig.transmissionRiskLevelFilters)) { + Timber.d( + "%s dropped due to transmission risk level filter, level is %s", + exposureWindow, + transmissionRiskLevel + ) + return null + } + + val transmissionRiskValue = + transmissionRiskLevel * appConfig.transmissionRiskLevelMultiplier + + Timber.d( + "%s's transmissionRiskValue is: %s", + exposureWindow, + transmissionRiskValue + ) + + val weightedMinutes = determineWeightedSeconds( + exposureWindow, + appConfig.minutesAtAttenuationWeights + ) / 60 + + Timber.d( + "%s's weightedMinutes are: %s", + exposureWindow, + weightedMinutes + ) + + val normalizedTime = transmissionRiskValue * weightedMinutes + + Timber.d( + "%s's normalizedTime is: %s", + exposureWindow, + normalizedTime + ) + + val riskLevel = determineRiskLevel( + normalizedTime, + appConfig.normalizedTimePerExposureWindowToRiskLevelMapping + ) + + if (riskLevel == null) { + Timber.e("Exposure Window: $exposureWindow could not be mapped to a risk level") + throw NormalizedTimePerExposureWindowToRiskLevelMappingMissingException() + } + + Timber.d( + "%s's riskLevel is: %s", + exposureWindow, + riskLevel + ) + + return RiskResult(transmissionRiskLevel, normalizedTime, riskLevel) + } + + override fun aggregateResults( + exposureWindowsAndResult: Map + ): AggregatedRiskResult { + val uniqueDatesMillisSinceEpoch = exposureWindowsAndResult.keys + .map { it.dateMillisSinceEpoch } + .toSet() + + Timber.d( + "uniqueDates: ${ + TextUtils.join(System.lineSeparator(), uniqueDatesMillisSinceEpoch) + }" + ) + val exposureHistory = uniqueDatesMillisSinceEpoch.map { + aggregateRiskPerDate(it, exposureWindowsAndResult) + } + + Timber.d("exposureHistory size: ${exposureHistory.size}") + + // 6. Determine `Total Risk` + val totalRiskLevel = + if (exposureHistory.any { + it.riskLevel == RiskCalculationParametersOuterClass + .NormalizedTimeToRiskLevelMapping + .RiskLevel + .HIGH + }) { + RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH + } else { + RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW + } + + Timber.d("totalRiskLevel: ${totalRiskLevel.name} (${totalRiskLevel.ordinal})") + + // 7. Determine `Date of Most Recent Date with Low Risk` + val mostRecentDateWithLowRisk = mostRecentDateForRisk( + exposureHistory, + RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW + ) + + Timber.d("mostRecentDateWithLowRisk: $mostRecentDateWithLowRisk") + + // 8. Determine `Date of Most Recent Date with High Risk` + val mostRecentDateWithHighRisk = mostRecentDateForRisk( + exposureHistory, + RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH + ) + + Timber.d("mostRecentDateWithHighRisk: $mostRecentDateWithHighRisk") + + // 9. Determine `Total Minimum Distinct Encounters With Low Risk` + val totalMinimumDistinctEncountersWithLowRisk = exposureHistory + .sumBy { it.minimumDistinctEncountersWithLowRisk } + + Timber.d("totalMinimumDistinctEncountersWithLowRisk: $totalMinimumDistinctEncountersWithLowRisk") + + // 10. Determine `Total Minimum Distinct Encounters With High Risk` + val totalMinimumDistinctEncountersWithHighRisk = exposureHistory + .sumBy { it.minimumDistinctEncountersWithHighRisk } + + Timber.d("totalMinimumDistinctEncountersWithHighRisk: $totalMinimumDistinctEncountersWithHighRisk") + + return AggregatedRiskResult( + totalRiskLevel, + totalMinimumDistinctEncountersWithLowRisk, + totalMinimumDistinctEncountersWithHighRisk, + mostRecentDateWithLowRisk, + mostRecentDateWithHighRisk + ) + } + + private fun mostRecentDateForRisk( + exposureHistory: List, + riskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel + ): Instant? = exposureHistory + .filter { it.riskLevel == riskLevel } + .maxOfOrNull { it.dateMillisSinceEpoch } + ?.let { Instant.ofEpochMilli(it) } + + private fun aggregateRiskPerDate( + dateMillisSinceEpoch: Long, + exposureWindowsAndResult: Map + ): AggregatedRiskPerDateResult { + // 1. Group `Exposure Windows by Date` + val exposureWindowsAndResultForDate = exposureWindowsAndResult + .filter { it.key.dateMillisSinceEpoch == dateMillisSinceEpoch } + + // 2. Determine `Normalized Time per Date` + val normalizedTime = exposureWindowsAndResultForDate.values + .sumOf { it.normalizedTime } + + Timber.d("Aggregating result for date $dateMillisSinceEpoch - ${Instant.ofEpochMilli(dateMillisSinceEpoch)}") + + // 3. Determine `Risk Level per Date` + val riskLevel = try { + appConfig.normalizedTimePerDayToRiskLevelMappingList + .filter { it.normalizedTimeRange.inRange(normalizedTime) } + .map { it.riskLevel } + .first() + } catch (e: Exception) { + throw ApplicationConfigurationInvalidException( + e, + "Invalid config for normalizedTimePerDayToRiskLevelMapping" + ) + } + + Timber.d("riskLevel: ${riskLevel.name} (${riskLevel.ordinal})") + + // 4. Determine `Minimum Distinct Encounters With Low Risk per Date` + val minimumDistinctEncountersWithLowRisk = minimumDistinctEncountersForRisk( + exposureWindowsAndResultForDate, + RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW + ) + + Timber.d("minimumDistinctEncountersWithLowRisk: $minimumDistinctEncountersWithLowRisk") + + // 5. Determine `Minimum Distinct Encounters With High Risk per Date` + val minimumDistinctEncountersWithHighRisk = minimumDistinctEncountersForRisk( + exposureWindowsAndResultForDate, + RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH + ) + + Timber.d("minimumDistinctEncountersWithHighRisk: $minimumDistinctEncountersWithHighRisk") + + return AggregatedRiskPerDateResult( + dateMillisSinceEpoch, + riskLevel, + minimumDistinctEncountersWithLowRisk, + minimumDistinctEncountersWithHighRisk + ) + } + + private fun minimumDistinctEncountersForRisk( + exposureWindowsAndResultForDate: Map, + riskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel + ): Int = + exposureWindowsAndResultForDate + .filter { it.value.riskLevel == riskLevel } + .map { "${it.value.transmissionRiskLevel}_${it.key.calibrationConfidence}" } + .distinct() + .size + companion object { private val TAG = DefaultRiskLevels::class.java.simpleName private const val DECIMAL_MULTIPLIER = 100 + + class NormalizedTimePerExposureWindowToRiskLevelMappingMissingException : Exception() + class UnknownReportTypeException : Exception() + + private fun RiskCalculationParametersOuterClass.Range.inRange(value: T): Boolean = + when { + minExclusive && value.toDouble() <= min -> false + !minExclusive && value.toDouble() < min -> false + maxExclusive && value.toDouble() >= max -> false + !maxExclusive && value.toDouble() > max -> false + else -> true + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt index 407fef631dd..29ab44cbd70 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt @@ -124,18 +124,18 @@ class RiskLevelTask @Inject constructor( } private suspend fun backgroundJobsEnabled() = - backgroundModeStatus.isAutoModeEnabled.first().also { - if (it) { - Timber.tag(TAG) - .v("diagnosis keys outdated and active tracing time is above threshold") - Timber.tag(TAG) - .v("manual mode not active (background jobs enabled)") - } else { - Timber.tag(TAG) - .v("diagnosis keys outdated and active tracing time is above threshold") - Timber.tag(TAG).v("manual mode active (background jobs disabled)") - } + backgroundModeStatus.isAutoModeEnabled.first().also { + if (it) { + Timber.tag(TAG) + .v("diagnosis keys outdated and active tracing time is above threshold") + Timber.tag(TAG) + .v("manual mode not active (background jobs enabled)") + } else { + Timber.tag(TAG) + .v("diagnosis keys outdated and active tracing time is above threshold") + Timber.tag(TAG).v("manual mode active (background jobs disabled)") } + } override suspend fun cancel() { Timber.w("cancel() called.") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt index b8cd2f00c6b..28cdbf181ce 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt @@ -1,6 +1,9 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureSummary +import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.RiskResult import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass interface RiskLevels { @@ -22,8 +25,17 @@ interface RiskLevels { time: Long ) + @Deprecated("Switch to new calculation with Exposure Window") fun calculateRiskScore( attenuationParameters: AttenuationDurationOuterClass.AttenuationDuration, exposureSummary: ExposureSummary ): Double + + fun calculateRisk( + exposureWindow: ExposureWindow + ): RiskResult? + + fun aggregateResults( + exposureWindowsAndResult: Map + ): AggregatedRiskResult } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResult.kt new file mode 100644 index 00000000000..99c140888f2 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskPerDateResult.kt @@ -0,0 +1,10 @@ +package de.rki.coronawarnapp.risk.result + +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass + +data class AggregatedRiskPerDateResult( + val dateMillisSinceEpoch: Long, + val riskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel, + val minimumDistinctEncountersWithLowRisk: Int, + val minimumDistinctEncountersWithHighRisk: Int +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt new file mode 100644 index 00000000000..17e48ce7644 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt @@ -0,0 +1,12 @@ +package de.rki.coronawarnapp.risk.result + +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass +import org.joda.time.Instant + +data class AggregatedRiskResult( + val totalRiskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel, + val totalMinimumDistinctEncountersWithLowRisk: Int, + val totalMinimumDistinctEncountersWithHighRisk: Int, + val mostRecentDateWithLowRisk: Instant?, + val mostRecentDateWithHighRisk: Instant? +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/RiskResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/RiskResult.kt new file mode 100644 index 00000000000..e2b5c582b34 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/RiskResult.kt @@ -0,0 +1,10 @@ +package de.rki.coronawarnapp.risk.result + +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass + +// TODO("Adjust Types") +data class RiskResult( + val transmissionRiskLevel: Int, + val normalizedTime: Double, + val riskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt index 18ee07f72f8..55e68f895f3 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.appconfig.mapping import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig +import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.KeyDownloadConfig import de.rki.coronawarnapp.appconfig.RiskCalculationConfig import io.mockk.MockKAnnotations @@ -21,6 +22,7 @@ class ConfigParserTest : BaseTest() { @MockK lateinit var keyDownloadConfigMapper: KeyDownloadConfig.Mapper @MockK lateinit var exposureDetectionConfigMapper: ExposureDetectionConfig.Mapper @MockK lateinit var riskCalculationConfigMapper: RiskCalculationConfig.Mapper + @MockK lateinit var exposureWindowRiskCalculationConfigMapper: ExposureWindowRiskCalculationConfig.Mapper @BeforeEach fun setup() { @@ -30,6 +32,7 @@ class ConfigParserTest : BaseTest() { every { keyDownloadConfigMapper.map(any()) } returns mockk() every { exposureDetectionConfigMapper.map(any()) } returns mockk() every { riskCalculationConfigMapper.map(any()) } returns mockk() + every { exposureWindowRiskCalculationConfigMapper.map(any()) } returns mockk() } @AfterEach @@ -41,7 +44,8 @@ class ConfigParserTest : BaseTest() { cwaConfigMapper = cwaConfigMapper, keyDownloadConfigMapper = keyDownloadConfigMapper, exposureDetectionConfigMapper = exposureDetectionConfigMapper, - riskCalculationConfigMapper = riskCalculationConfigMapper + riskCalculationConfigMapper = riskCalculationConfigMapper, + exposureWindowRiskCalculationConfigMapper = exposureWindowRiskCalculationConfigMapper ) @Test @@ -53,6 +57,7 @@ class ConfigParserTest : BaseTest() { keyDownloadConfigMapper.map(any()) exposureDetectionConfigMapper.map(any()) riskCalculationConfigMapper.map(any()) + exposureWindowRiskCalculationConfigMapper.map(any()) } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt index 845722a2c23..746c8acf70b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt @@ -5,7 +5,10 @@ import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk import junit.framework.TestCase.assertEquals import org.junit.Before import org.junit.Test @@ -19,6 +22,10 @@ class RiskLevelsTest : BaseTest() { @Before fun setUp() { MockKAnnotations.init(this) + + coEvery { appConfigProvider.getAppConfig() } returns mockk() + every { appConfigProvider.currentConfig } returns mockk() + riskLevels = DefaultRiskLevels(appConfigProvider) } From 3687be816be88502e8647456daede4eb3bf8782c Mon Sep 17 00:00:00 2001 From: AlexanderAlferov <64849422+AlexanderAlferov@users.noreply.github.com> Date: Thu, 12 Nov 2020 18:27:10 +0300 Subject: [PATCH 03/47] Parse test JSON files and init unit tests with respective parameters (EXPOSUREAPP-3456) (#1575) * Split and hide the protobuf config behind interfaces with individual mappers responsible for creating the desired formats. * Merge branch 'release/1.7.x' into feature/3455-more-frequent-riskscore-updates-configs # Conflicts: # Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt # Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RiskLevelTransaction.kt # Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RiskLevelTransactionTest.kt * Make the AppConfig observable. Provide the server time offset. Offer a lastUpdatedAt timestamp. Add an app config specific test screen. Clean up test screens a bit and move debug options out of API test options. * Fix test regression due to refactoring (moved code around). * Store the server timestamp and offset at retrieval. Switch to config storage via json to be able to store additional meta data fields (i.e. time). * KLint and Me have a hate relationship based on both mutual admiration. * Fix time offset parsing being locale dependent. * Fix broken unit tests. * Improve offset accuracy, move before unzipping. * Fix overly long livedata subscription to results (viewmodel scope vs observer scope) * Add mapping for the new protobuf configs + tests. * For cached (retrofit) response, we need to check the cacheResponse and its timestamps to determine an accurate time offset. * Exposure a boolean property to tell us when a fallback config is being used. * Hide the observable flow behind a method that can automatically triggers refreshes. * Use a common mapper interface. * set old risklevelcalculation deprecated * Created skeleton for new risk calculation and aggregation * Initial * Implementing steps to aggregate results form exposure windows - wip * Address PR comments and KLints. * Fix refactoring regression. * ktlint * Json parsing * Added ExposureWindowRiskLevelConfig and ExposureWindowRiskLevelConfigMapper for new config api (not yet introduced) Signed-off-by: Kolya Opahle * Added first Implementation of exposure window based calculateRisk function Signed-off-by: Kolya Opahle * Added generics to Range.inRange Signed-off-by: Kolya Opahle * Added Ugly Hack to RiskLevelTransaction to allow for compilation during testing Signed-off-by: Kolya Opahle * Linting and injecting RiskLevelCalculation into TestRiskLevelCalculationFragmentCWAViewModel, currently wont build because ExposureWindowRiskLevelConfig has no Provider Signed-off-by: Kolya Opahle * Linting extravaganza Signed-off-by: Kolya Opahle * Lint Wars Episode VI: Return of the trailing Comma * Improve config unzipping code. * Add flag to forward exception thrown during HotDataFlow.kt initialization. * Don't specify a default context via singleton. * Move download and fallback logic into it's own class just responsible for sourcing the config: "AppConfigSource". "AppConfigProvider" is now only responsible for making it available. * Check test cases * Simplify current concepts for making the app config observable until we have a default configuration. * Implementing steps to aggregate results form exposure windows * cleaned todo * Adjusted default values * Improve app config test screen, delete options, better feedback. Show toast instead of crash on errors. * Fixed GSON serialization not encoding/decoding the byte array correctly. Added specific type adapters for instant and duration to get cleaner json. * Remove type adapters from base gson due to conflict with CalculationTrackerStorage. * refactored Windows aggregation * We want to default to forced serialization of instant by our converters, instead of using the default serialization which will differ between Java8.Instant and JodaTime.Instant, to prevent future headaches there, register explicit converters by default, and overwrite them if necessary (currently only needed for CalculationTrackerStorage.kt). * Improve AppConfigServer code readability by moving code into extensions. * Fix merge conflicts * removed example value * Added missing import to WorkerBinderTest * fixed unit tests * Removed auto formatting on unrelated files (revert + cherry pick in other commit) Signed-off-by: Kolya Opahle * Implementing steps to aggregate results form exposure windows * Renamed ExposureWindowRiskLevelConfig to ExposureWindowRiskCalculationConfig * adjusted & refactored Windows aggregation * removed example Values * satisfy lint * make Aggregation work with Instant now * Use long while calculation * Added normalizedTimePerDayToRiskLevelMappingList to AppConfig * normalizedTimePerDayToRiskLevelMappingList from AppConfig * satisfy lint * Get AppConfig on init and listen for updates * exposureData to aggregatedRiskPerDateResult * Corrected name in ConfigParserTest * use instant for specific aggregation logs * satisfy CI * satisfy detekt * Mock exposure windows * Full test process * Fix gitignore * Improved logging * Correct test cases dates handling * Config fix and logs * Small clean up * fixed some naming and conversion issues with json test case parsing Signed-off-by: Kolya Opahle * TRL Encodings in config did not match TRL Encodings used in js example * Removing a return that broke the calculation * Actual tests and formatting * Formatting Co-authored-by: Matthias Urhahn Co-authored-by: BMItter Co-authored-by: Kolya Opahle Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> --- .../coronawarnapp/risk/DefaultRiskLevels.kt | 2 +- .../windows/ExposureWindowsCalculationTest.kt | 402 +++++++ .../entities/ExposureWindowsJsonInput.kt | 14 + .../entities/cases/JsonScanInstance.kt | 12 + .../windows/entities/cases/JsonWindow.kt | 16 + .../nearby/windows/entities/cases/TestCase.kt | 20 + .../DefaultRiskCalculationConfiguration.kt | 20 + .../JsonMinutesAtAttenuationFilter.kt | 10 + .../JsonMinutesAtAttenuationWeight.kt | 10 + .../JsonNormalizedTimeToRiskLevelMapping.kt | 10 + .../entities/configuration/JsonTrlEncoding.kt | 18 + .../entities/configuration/JsonTrlFilter.kt | 8 + .../windows/entities/configuration/Range.kt | 14 + .../exposure-windows-risk-calculation.json | 1025 +++++++++++++++++ 14 files changed, 1580 insertions(+), 1 deletion(-) create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/ExposureWindowsJsonInput.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonScanInstance.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonWindow.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationFilter.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationWeight.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonNormalizedTimeToRiskLevelMapping.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlEncoding.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlFilter.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/Range.kt create mode 100644 Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index ada0d0cc078..146f92532b7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -308,7 +308,7 @@ class DefaultRiskLevels @Inject constructor( .filter { it.attenuationRange.inRange(scanInstance.typicalAttenuationDb) } .map { it.weight } .firstOrNull() ?: .0 - return seconds + scanInstance.secondsSinceLastScan * weight + seconds + scanInstance.secondsSinceLastScan * weight } private fun determineRiskLevel( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt new file mode 100644 index 00000000000..3dab010d2aa --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -0,0 +1,402 @@ +package de.rki.coronawarnapp.nearby.windows + +import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import com.google.android.gms.nearby.exposurenotification.ScanInstance +import com.google.gson.Gson +import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.appconfig.ConfigData +import de.rki.coronawarnapp.appconfig.DefaultConfigData +import de.rki.coronawarnapp.nearby.windows.entities.ExposureWindowsJsonInput +import de.rki.coronawarnapp.nearby.windows.entities.cases.JsonScanInstance +import de.rki.coronawarnapp.nearby.windows.entities.cases.JsonWindow +import de.rki.coronawarnapp.nearby.windows.entities.cases.TestCase +import de.rki.coronawarnapp.nearby.windows.entities.configuration.DefaultRiskCalculationConfiguration +import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonMinutesAtAttenuationFilter +import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonMinutesAtAttenuationWeight +import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonNormalizedTimeToRiskLevelMapping +import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonTrlFilter +import de.rki.coronawarnapp.risk.DefaultRiskLevels +import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.result.RiskResult +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.serialization.fromJson +import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.runBlocking +import org.joda.time.DateTimeConstants +import org.joda.time.Duration +import org.joda.time.Instant +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import timber.log.Timber +import java.io.FileReader +import java.nio.file.Paths + +class ExposureWindowsCalculationTest : BaseTest() { + + @MockK lateinit var appConfigProvider: AppConfigProvider + @MockK lateinit var configData: ConfigData + @MockK lateinit var timeStamper: TimeStamper + + private lateinit var riskLevels: DefaultRiskLevels + private lateinit var testConfig: ConfigData + + // Json file (located in /test/resources/exposure-windows-risk-calculation.json) + private val fileName = "exposure-windows-risk-calculation.json" + + // Debug logs + private enum class LogLevel { + NONE, + ONLY_COMPARISON, + EXTENDED, + ALL + } + private val logLevel = LogLevel.ONLY_COMPARISON + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + every { timeStamper.nowUTC } returns Instant.now() + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun debugLog(s: String, toShow: LogLevel = LogLevel.ALL) { + if (logLevel < toShow) + return + Timber.v(s) + } + + @Test + fun `one test to rule them all`(): Unit = runBlocking { + // 1 - Load and parse json file + val jsonFile = Paths.get("src", "test", "resources", fileName).toFile() + jsonFile shouldNotBe null + val jsonString = FileReader(jsonFile).readText() + jsonString.length shouldBeGreaterThan 0 + val json = Gson().fromJson(jsonString) + json shouldNotBe null + + // 2 - Check test cases + for (case: TestCase in json.testCases) { + checkTestCase(case) + } + debugLog("Test cases checked. Total count: ${json.testCases.size}") + + // 3 - Mock calculation configuration and create default risk level with it + jsonToConfiguration(json.defaultRiskCalculationConfiguration) + coEvery { appConfigProvider.getAppConfig() } returns testConfig + every { appConfigProvider.currentConfig } returns flow { testConfig } + logConfiguration(testConfig) + riskLevels = DefaultRiskLevels(appConfigProvider) + + // 4 - Mock and log exposure windows + val allExposureWindows = mutableListOf() + for (case: TestCase in json.testCases) { + val exposureWindows: List = + case.exposureWindows.map { window -> jsonToExposureWindow(window) } + allExposureWindows.addAll(exposureWindows) + + // 5 - Calculate risk level for test case and aggregate results + val exposureWindowsAndResult = HashMap() + for (exposureWindow: ExposureWindow in exposureWindows) { + + logExposureWindow(exposureWindow, "➡➡ EXPOSURE WINDOW PASSED ➡➡", LogLevel.EXTENDED) + val riskResult = riskLevels.calculateRisk(exposureWindow) ?: continue + exposureWindowsAndResult[exposureWindow] = riskResult + } + debugLog("Exposure windows and result: ${exposureWindowsAndResult.size}") + + val aggregatedRiskResult = riskLevels.aggregateResults(exposureWindowsAndResult) + + debugLog( + "\n" + comparisonDebugTable(aggregatedRiskResult, case), + LogLevel.ONLY_COMPARISON + ) + + // 6 - Check with expected result from test case + aggregatedRiskResult.totalRiskLevel.number shouldBe case.expTotalRiskLevel + aggregatedRiskResult.mostRecentDateWithHighRisk shouldBe getTestCaseDate(case.expAgeOfMostRecentDateWithHighRisk) + aggregatedRiskResult.mostRecentDateWithLowRisk shouldBe getTestCaseDate(case.expAgeOfMostRecentDateWithLowRisk) + aggregatedRiskResult.totalMinimumDistinctEncountersWithHighRisk shouldBe case.expTotalMinimumDistinctEncountersWithHighRisk + aggregatedRiskResult.totalMinimumDistinctEncountersWithLowRisk shouldBe case.expTotalMinimumDistinctEncountersWithLowRisk + } + } + + private fun getTestCaseDate(expAge: Long?): Instant? { + if (expAge == null) return null + return timeStamper.nowUTC - expAge * DateTimeConstants.MILLIS_PER_DAY + } + + private fun comparisonDebugTable(aggregated: AggregatedRiskResult, case: TestCase): String { + val result = StringBuilder() + result.append("\n").append(case.description) + result.append("\n").append("+----------------------+--------------------------+--------------------------+") + result.append("\n").append("| Property | Actual | Expected |") + result.append("\n").append("+----------------------+--------------------------+--------------------------+") + result.append( + addPropertyCheckToComparisonDebugTable( + "Total Risk", + aggregated.totalRiskLevel.number, + case.expTotalRiskLevel + ) + ) + result.append( + addPropertyCheckToComparisonDebugTable( + "Date With High Risk", + aggregated.mostRecentDateWithHighRisk, + getTestCaseDate(case.expAgeOfMostRecentDateWithHighRisk) + ) + ) + result.append( + addPropertyCheckToComparisonDebugTable( + "Date With Low Risk", + aggregated.mostRecentDateWithLowRisk, + getTestCaseDate(case.expAgeOfMostRecentDateWithLowRisk) + ) + ) + result.append( + addPropertyCheckToComparisonDebugTable( + "Encounters High Risk", + aggregated.totalMinimumDistinctEncountersWithHighRisk, + case.expTotalMinimumDistinctEncountersWithHighRisk + ) + ) + result.append( + addPropertyCheckToComparisonDebugTable( + "Encounters Low Risk", + aggregated.totalMinimumDistinctEncountersWithLowRisk, + case.expTotalMinimumDistinctEncountersWithLowRisk + ) + ) + result.append("\n") + return result.toString() + } + + private fun addPropertyCheckToComparisonDebugTable(propertyName: String, expected: Any?, actual: Any?): String { + val format = "| %-20s | %-24s | %-24s |" + val result = StringBuilder() + result.append("\n").append(String.format(format, propertyName, expected, actual)) + result.append("\n").append("+----------------------+--------------------------+--------------------------+") + return result.toString() + } + + private fun checkTestCase(case: TestCase) { + debugLog("Checking ${case.description}", LogLevel.ALL) + case.expTotalRiskLevel shouldNotBe null + case.expTotalMinimumDistinctEncountersWithLowRisk shouldNotBe null + case.expTotalMinimumDistinctEncountersWithHighRisk shouldNotBe null + case.exposureWindows.map { exposureWindow -> checkExposureWindow(exposureWindow) } + } + + private fun checkExposureWindow(jsonWindow: JsonWindow) { + jsonWindow.ageInDays shouldNotBe null + jsonWindow.reportType shouldNotBe null + jsonWindow.infectiousness shouldNotBe null + jsonWindow.calibrationConfidence shouldNotBe null + } + + private fun logConfiguration(config: ConfigData) { + val result = StringBuilder() + result.append("\n\n").append("----------------- \uD83D\uDEE0 CONFIGURATION \uD83D\uDEE0 -----------") + + result.append("\n").append("◦ Minutes At Attenuation Filters (${config.minutesAtAttenuationFilters.size})") + for (filter: RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter in config.minutesAtAttenuationFilters) { + result.append("\n\t").append("⇥ Filter") + result.append(logRange(filter.attenuationRange, "Attenuation Range")) + result.append(logRange(filter.dropIfMinutesInRange, "Drop If Minutes In Range")) + } + + result.append("\n").append("◦ Minutes At Attenuation Weights (${config.minutesAtAttenuationWeights.size})") + for (weight: RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight in config.minutesAtAttenuationWeights) { + result.append("\n\t").append("⇥ Weight") + result.append(logRange(weight.attenuationRange, "Attenuation Range")) + result.append("\n\t\t").append("↳ Weight: ${weight.weight}") + } + + result.append("\n").append("◦ Normalized Time Per Day To Risk Level Mapping List (${config.normalizedTimePerDayToRiskLevelMappingList.size})") + for (mapping: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping in config.normalizedTimePerDayToRiskLevelMappingList) { + result.append("\n\t").append("⇥ Mapping") + result.append(logRange(mapping.normalizedTimeRange, "Normalized Time Range")) + result.append("\n\t\t").append("↳ Risk Level: ${mapping.riskLevel}") + } + + result.append("\n").append("◦ Normalized Time Per Exposure Window To Risk Level Mapping (${config.normalizedTimePerExposureWindowToRiskLevelMapping.size})") + for (mapping: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping in config.normalizedTimePerExposureWindowToRiskLevelMapping) { + result.append("\n\t").append("⇥ Mapping") + result.append(logRange(mapping.normalizedTimeRange, "Normalized Time Range")) + result.append("\n\t\t").append("↳ Risk Level: ${mapping.riskLevel}") + } + + result.append("\n").append("◦ Transmission Risk Level Encoding:") + result.append("\n\t").append("↳ Infectiousness Offset High: ${config.transmissionRiskLevelEncoding.infectiousnessOffsetHigh}") + result.append("\n\t").append("↳ Infectiousness Offset Standard: ${config.transmissionRiskLevelEncoding.infectiousnessOffsetStandard}") + result.append("\n\t").append("↳ Report Type Offset Confirmed Clinical Diagnosis: ${config.transmissionRiskLevelEncoding.reportTypeOffsetConfirmedClinicalDiagnosis}") + result.append("\n\t").append("↳ Report Type Offset Confirmed Test: ${config.transmissionRiskLevelEncoding.reportTypeOffsetConfirmedTest}") + result.append("\n\t").append("↳ Report Type Offset Recursive: ${config.transmissionRiskLevelEncoding.reportTypeOffsetRecursive}") + result.append("\n\t").append("↳ Report Type Offset Self Report: ${config.transmissionRiskLevelEncoding.reportTypeOffsetSelfReport}") + + result.append("\n").append("◦ Transmission Risk Level Filters (${config.transmissionRiskLevelFilters.size})") + for (filter: RiskCalculationParametersOuterClass.TrlFilter in config.transmissionRiskLevelFilters) { + result.append("\n\t").append("⇥ Trl Filter") + result.append(logRange(filter.dropIfTrlInRange, "Drop If Trl In Range")) + } + + result.append("\n").append("◦ Transmission Risk Level Multiplier: ${config.transmissionRiskLevelMultiplier}") + result.append("\n").append("-------------------------------------------- ⚙ -").append("\n") + debugLog(result.toString(), LogLevel.NONE) + } + + private fun logRange(range: RiskCalculationParametersOuterClass.Range, rangeName: String): String { + val builder = StringBuilder() + builder.append("\n\t\t").append("⇥ $rangeName") + builder.append("\n\t\t\t").append("↳ Min: ${range.min}") + builder.append("\n\t\t\t").append("↳ Max: ${range.max}") + builder.append("\n\t\t\t").append("↳ Min Exclusive: ${range.minExclusive}") + builder.append("\n\t\t\t").append("↳ Max Exclusive: ${range.maxExclusive}") + return builder.toString() + } + + private fun logExposureWindow(exposureWindow: ExposureWindow, title: String, logLevel: LogLevel = LogLevel.ALL) { + val result = StringBuilder() + result.append("\n\n").append("------------ $title -----------") + result.append("\n").append("Mocked Exposure window: #${exposureWindow.hashCode()}") + result.append("\n").append("◦ Calibration Confidence: ${exposureWindow.calibrationConfidence}") + result.append("\n").append("◦ Date Millis Since Epoch: ${exposureWindow.dateMillisSinceEpoch}") + result.append("\n").append("◦ Infectiousness: ${exposureWindow.infectiousness}") + result.append("\n").append("◦ Report type: ${exposureWindow.reportType}") + + result.append("\n").append("‣ Scan Instances (${exposureWindow.scanInstances.size}):") + for (scan: ScanInstance in exposureWindow.scanInstances) { + result.append("\n\t").append("⇥ Mocked Scan Instance: #${scan.hashCode()}") + result.append("\n\t\t").append("↳ Min Attenuation: ${scan.minAttenuationDb}") + result.append("\n\t\t").append("↳ Seconds Since Last Scan: ${scan.secondsSinceLastScan}") + result.append("\n\t\t").append("↳ Typical Attenuation: ${scan.typicalAttenuationDb}") + } + result.append("\n").append("-------------------------------------------- ✂ ----").append("\n") + debugLog(result.toString(), logLevel) + } + + private fun jsonToConfiguration(json: DefaultRiskCalculationConfiguration) { + + testConfig = DefaultConfigData( + serverTime = Instant.now(), + localOffset = Duration.ZERO, + mappedConfig = configData, + isFallback = false + ) + + val attenuationFilters = mutableListOf() + for (jsonFilter: JsonMinutesAtAttenuationFilter in json.minutesAtAttenuationFilters) { + val filter: RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter = mockk() + every { filter.attenuationRange.min } returns jsonFilter.attenuationRange.min + every { filter.attenuationRange.max } returns jsonFilter.attenuationRange.max + every { filter.attenuationRange.minExclusive } returns jsonFilter.attenuationRange.minExclusive + every { filter.attenuationRange.maxExclusive } returns jsonFilter.attenuationRange.maxExclusive + every { filter.dropIfMinutesInRange.min } returns jsonFilter.dropIfMinutesInRange.min + every { filter.dropIfMinutesInRange.max } returns jsonFilter.dropIfMinutesInRange.max + every { filter.dropIfMinutesInRange.minExclusive } returns jsonFilter.dropIfMinutesInRange.minExclusive + every { filter.dropIfMinutesInRange.maxExclusive } returns jsonFilter.dropIfMinutesInRange.maxExclusive + attenuationFilters.add(filter) + } + every { testConfig.minutesAtAttenuationFilters } returns attenuationFilters + + val attenuationWeights = mutableListOf() + for (jsonWeight: JsonMinutesAtAttenuationWeight in json.minutesAtAttenuationWeights) { + val weight: RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight = mockk() + every { weight.attenuationRange.min } returns jsonWeight.attenuationRange.min + every { weight.attenuationRange.max } returns jsonWeight.attenuationRange.max + every { weight.attenuationRange.minExclusive } returns jsonWeight.attenuationRange.minExclusive + every { weight.attenuationRange.maxExclusive } returns jsonWeight.attenuationRange.maxExclusive + every { weight.weight } returns jsonWeight.weight + attenuationWeights.add(weight) + } + every { testConfig.minutesAtAttenuationWeights } returns attenuationWeights + + val normalizedTimePerDayToRiskLevelMapping = mutableListOf< RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>() + for (jsonMapping: JsonNormalizedTimeToRiskLevelMapping in json.normalizedTimePerDayToRiskLevelMapping) { + val mapping: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping = mockk() + every { mapping.riskLevel } returns RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.forNumber(jsonMapping.riskLevel) + every { mapping.normalizedTimeRange.min } returns jsonMapping.normalizedTimeRange.min + every { mapping.normalizedTimeRange.max } returns jsonMapping.normalizedTimeRange.max + every { mapping.normalizedTimeRange.minExclusive } returns jsonMapping.normalizedTimeRange.minExclusive + every { mapping.normalizedTimeRange.maxExclusive } returns jsonMapping.normalizedTimeRange.maxExclusive + normalizedTimePerDayToRiskLevelMapping.add(mapping) + } + every { testConfig.normalizedTimePerDayToRiskLevelMappingList } returns normalizedTimePerDayToRiskLevelMapping + + val normalizedTimePerExposureWindowToRiskLevelMapping = mutableListOf< RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>() + for (jsonMapping: JsonNormalizedTimeToRiskLevelMapping in json.normalizedTimePerEWToRiskLevelMapping) { + val mapping: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping = mockk() + every { mapping.riskLevel } returns RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.forNumber(jsonMapping.riskLevel) + every { mapping.normalizedTimeRange.min } returns jsonMapping.normalizedTimeRange.min + every { mapping.normalizedTimeRange.max } returns jsonMapping.normalizedTimeRange.max + every { mapping.normalizedTimeRange.minExclusive } returns jsonMapping.normalizedTimeRange.minExclusive + every { mapping.normalizedTimeRange.maxExclusive } returns jsonMapping.normalizedTimeRange.maxExclusive + normalizedTimePerExposureWindowToRiskLevelMapping.add(mapping) + } + every { testConfig.normalizedTimePerExposureWindowToRiskLevelMapping } returns normalizedTimePerExposureWindowToRiskLevelMapping + + every { testConfig.transmissionRiskLevelMultiplier } returns json.transmissionRiskLevelMultiplier + + val trlEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding = mockk() + every { trlEncoding.infectiousnessOffsetHigh } returns json.trlEncoding.infectiousnessOffsetHigh + every { trlEncoding.infectiousnessOffsetStandard } returns json.trlEncoding.infectiousnessOffsetStandard + every { trlEncoding.reportTypeOffsetConfirmedClinicalDiagnosis } returns json.trlEncoding.reportTypeOffsetConfirmedClinicalDiagnosis + every { trlEncoding.reportTypeOffsetConfirmedTest } returns json.trlEncoding.reportTypeOffsetConfirmedTest + every { trlEncoding.reportTypeOffsetRecursive } returns json.trlEncoding.reportTypeOffsetRecursive + every { trlEncoding.reportTypeOffsetSelfReport } returns json.trlEncoding.reportTypeOffsetSelfReport + every { testConfig.transmissionRiskLevelEncoding } returns trlEncoding + + val trlFilters = mutableListOf() + for (jsonFilter: JsonTrlFilter in json.trlFilters) { + val filter: RiskCalculationParametersOuterClass.TrlFilter = mockk() + every { filter.dropIfTrlInRange.min } returns jsonFilter.dropIfTrlInRange.min + every { filter.dropIfTrlInRange.max } returns jsonFilter.dropIfTrlInRange.max + every { filter.dropIfTrlInRange.minExclusive } returns jsonFilter.dropIfTrlInRange.minExclusive + every { filter.dropIfTrlInRange.maxExclusive } returns jsonFilter.dropIfTrlInRange.maxExclusive + trlFilters.add(filter) + } + every { testConfig.transmissionRiskLevelFilters } returns trlFilters + } + + private fun jsonToExposureWindow(json: JsonWindow): ExposureWindow { + val exposureWindow: ExposureWindow = mockk() + + every { exposureWindow.calibrationConfidence } returns json.calibrationConfidence + every { exposureWindow.dateMillisSinceEpoch } returns timeStamper.nowUTC.millis - (DateTimeConstants.MILLIS_PER_DAY * json.ageInDays).toLong() + every { exposureWindow.infectiousness } returns json.infectiousness + every { exposureWindow.reportType } returns json.reportType + every { exposureWindow.scanInstances } returns json.scanInstances.map { scanInstance -> + jsonToScanInstance( + scanInstance + ) + } + + logExposureWindow(exposureWindow, "⊞ EXPOSURE WINDOW MOCK ⊞") + + return exposureWindow + } + + private fun jsonToScanInstance(json: JsonScanInstance): ScanInstance { + val scanInstance: ScanInstance = mockk() + every { scanInstance.minAttenuationDb } returns json.minAttenuation + every { scanInstance.secondsSinceLastScan } returns json.secondsSinceLastScan + every { scanInstance.typicalAttenuationDb } returns json.typicalAttenuation + return scanInstance + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/ExposureWindowsJsonInput.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/ExposureWindowsJsonInput.kt new file mode 100644 index 00000000000..3c0a77a3a27 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/ExposureWindowsJsonInput.kt @@ -0,0 +1,14 @@ +package de.rki.coronawarnapp.nearby.windows.entities + +import com.google.gson.annotations.SerializedName +import de.rki.coronawarnapp.nearby.windows.entities.cases.TestCase +import de.rki.coronawarnapp.nearby.windows.entities.configuration.DefaultRiskCalculationConfiguration + +data class ExposureWindowsJsonInput( + @SerializedName("__comment__") + val comment: String, + @SerializedName("defaultRiskCalculationConfiguration") + val defaultRiskCalculationConfiguration: DefaultRiskCalculationConfiguration, + @SerializedName("testCases") + val testCases: List +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonScanInstance.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonScanInstance.kt new file mode 100644 index 00000000000..a9ea714b7e7 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonScanInstance.kt @@ -0,0 +1,12 @@ +package de.rki.coronawarnapp.nearby.windows.entities.cases + +import com.google.gson.annotations.SerializedName + +data class JsonScanInstance( + @SerializedName("minAttenuation") + val minAttenuation: Int, + @SerializedName("secondsSinceLastScan") + val secondsSinceLastScan: Int, + @SerializedName("typicalAttenuation") + val typicalAttenuation: Int +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonWindow.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonWindow.kt new file mode 100644 index 00000000000..7394b329509 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/JsonWindow.kt @@ -0,0 +1,16 @@ +package de.rki.coronawarnapp.nearby.windows.entities.cases + +import com.google.gson.annotations.SerializedName + +data class JsonWindow( + @SerializedName("ageInDays") + val ageInDays: Int, + @SerializedName("calibrationConfidence") + val calibrationConfidence: Int, + @SerializedName("infectiousness") + val infectiousness: Int, + @SerializedName("reportType") + val reportType: Int, + @SerializedName("scanInstances") + val scanInstances: List +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt new file mode 100644 index 00000000000..fa8f45277f6 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt @@ -0,0 +1,20 @@ +package de.rki.coronawarnapp.nearby.windows.entities.cases + +import com.google.gson.annotations.SerializedName + +data class TestCase( + @SerializedName("description") + val description: String, + @SerializedName("expAgeOfMostRecentDateWithHighRisk") + val expAgeOfMostRecentDateWithHighRisk: Long?, + @SerializedName("expAgeOfMostRecentDateWithLowRisk") + val expAgeOfMostRecentDateWithLowRisk: Long?, + @SerializedName("expTotalMinimumDistinctEncountersWithHighRisk") + val expTotalMinimumDistinctEncountersWithHighRisk: Int, + @SerializedName("expTotalMinimumDistinctEncountersWithLowRisk") + val expTotalMinimumDistinctEncountersWithLowRisk: Int, + @SerializedName("expTotalRiskLevel") + val expTotalRiskLevel: Int, + @SerializedName("exposureWindows") + val exposureWindows: List +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt new file mode 100644 index 00000000000..7445d86e786 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/DefaultRiskCalculationConfiguration.kt @@ -0,0 +1,20 @@ +package de.rki.coronawarnapp.nearby.windows.entities.configuration + +import com.google.gson.annotations.SerializedName + +data class DefaultRiskCalculationConfiguration( + @SerializedName("minutesAtAttenuationFilters") + val minutesAtAttenuationFilters: List, + @SerializedName("minutesAtAttenuationWeights") + val minutesAtAttenuationWeights: List, + @SerializedName("normalizedTimePerDayToRiskLevelMapping") + val normalizedTimePerDayToRiskLevelMapping: List, + @SerializedName("normalizedTimePerEWToRiskLevelMapping") + val normalizedTimePerEWToRiskLevelMapping: List, + @SerializedName("transmissionRiskLevelMultiplier") + val transmissionRiskLevelMultiplier: Double, + @SerializedName("trlEncoding") + val trlEncoding: JsonTrlEncoding, + @SerializedName("trlFilters") + val trlFilters: List +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationFilter.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationFilter.kt new file mode 100644 index 00000000000..a01efc1f602 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationFilter.kt @@ -0,0 +1,10 @@ +package de.rki.coronawarnapp.nearby.windows.entities.configuration + +import com.google.gson.annotations.SerializedName + +data class JsonMinutesAtAttenuationFilter( + @SerializedName("attenuationRange") + val attenuationRange: Range, + @SerializedName("dropIfMinutesInRange") + val dropIfMinutesInRange: Range +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationWeight.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationWeight.kt new file mode 100644 index 00000000000..3af598da7dc --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonMinutesAtAttenuationWeight.kt @@ -0,0 +1,10 @@ +package de.rki.coronawarnapp.nearby.windows.entities.configuration + +import com.google.gson.annotations.SerializedName + +data class JsonMinutesAtAttenuationWeight( + @SerializedName("attenuationRange") + val attenuationRange: Range, + @SerializedName("weight") + val weight: Double +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonNormalizedTimeToRiskLevelMapping.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonNormalizedTimeToRiskLevelMapping.kt new file mode 100644 index 00000000000..4f0fc786998 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonNormalizedTimeToRiskLevelMapping.kt @@ -0,0 +1,10 @@ +package de.rki.coronawarnapp.nearby.windows.entities.configuration + +import com.google.gson.annotations.SerializedName + +data class JsonNormalizedTimeToRiskLevelMapping( + @SerializedName("normalizedTimeRange") + val normalizedTimeRange: Range, + @SerializedName("riskLevel") + val riskLevel: Int +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlEncoding.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlEncoding.kt new file mode 100644 index 00000000000..00a3e5af782 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlEncoding.kt @@ -0,0 +1,18 @@ +package de.rki.coronawarnapp.nearby.windows.entities.configuration + +import com.google.gson.annotations.SerializedName + +data class JsonTrlEncoding( + @SerializedName("infectiousnessOffsetHigh") + val infectiousnessOffsetHigh: Int, + @SerializedName("infectiousnessOffsetStandard") + val infectiousnessOffsetStandard: Int, + @SerializedName("reportTypeOffsetConfirmedClinicalDiagnosis") + val reportTypeOffsetConfirmedClinicalDiagnosis: Int, + @SerializedName("reportTypeOffsetConfirmedTest") + val reportTypeOffsetConfirmedTest: Int, + @SerializedName("reportTypeOffsetRecursive") + val reportTypeOffsetRecursive: Int, + @SerializedName("reportTypeOffsetSelfReport") + val reportTypeOffsetSelfReport: Int +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlFilter.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlFilter.kt new file mode 100644 index 00000000000..6e529bc6af3 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/JsonTrlFilter.kt @@ -0,0 +1,8 @@ +package de.rki.coronawarnapp.nearby.windows.entities.configuration + +import com.google.gson.annotations.SerializedName + +data class JsonTrlFilter( + @SerializedName("dropIfTrlInRange") + val dropIfTrlInRange: Range +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/Range.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/Range.kt new file mode 100644 index 00000000000..a4d686a74cd --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/configuration/Range.kt @@ -0,0 +1,14 @@ +package de.rki.coronawarnapp.nearby.windows.entities.configuration + +import com.google.gson.annotations.SerializedName + +data class Range( + @SerializedName("min") + val min: Double, + @SerializedName("minExclusive") + val minExclusive: Boolean, + @SerializedName("max") + val max: Double, + @SerializedName("maxExclusive") + val maxExclusive: Boolean +) diff --git a/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json new file mode 100644 index 00000000000..4338b8f94fc --- /dev/null +++ b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json @@ -0,0 +1,1025 @@ +{ + "__comment__": "JSON has been generated from YAML, see README", + "defaultRiskCalculationConfiguration": { + "minutesAtAttenuationFilters": [ + { + "attenuationRange": { + "min": 0, + "max": 73, + "maxExclusive": true + }, + "dropIfMinutesInRange": { + "min": 0, + "max": 10, + "maxExclusive": true + } + } + ], + "trlFilters": [ + { + "dropIfTrlInRange": { + "min": 1, + "max": 2 + } + } + ], + "minutesAtAttenuationWeights": [ + { + "attenuationRange": { + "min": 0, + "max": 55, + "maxExclusive": true + }, + "weight": 1 + }, + { + "attenuationRange": { + "min": 55, + "max": 63, + "maxExclusive": true + }, + "weight": 0.5 + } + ], + "normalizedTimePerEWToRiskLevelMapping": [ + { + "normalizedTimeRange": { + "min": 0, + "max": 15, + "maxExclusive": true + }, + "riskLevel": 1 + }, + { + "normalizedTimeRange": { + "min": 15, + "max": 9999 + }, + "riskLevel": 2 + } + ], + "normalizedTimePerDayToRiskLevelMapping": [ + { + "normalizedTimeRange": { + "min": 0, + "max": 15, + "maxExclusive": true + }, + "riskLevel": 1 + }, + { + "normalizedTimeRange": { + "min": 15, + "max": 9999 + }, + "riskLevel": 2 + } + ], + "trlEncoding": { + "infectiousnessOffsetStandard": 0, + "infectiousnessOffsetHigh": 4, + "reportTypeOffsetRecursive": 4, + "reportTypeOffsetSelfReport": 3, + "reportTypeOffsetConfirmedClinicalDiagnosis": 2, + "reportTypeOffsetConfirmedTest": 1 + }, + "transmissionRiskLevelMultiplier": 0.2 + }, + "testCases": [ + { + "description": "drop Exposure Windows that do not match minutesAtAttenuationFilters (< 10 minutes)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 2, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 299 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "keep Exposure Windows that match minutesAtAttenuationFilters (>= 10 minutes)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 2, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "drop Exposure Windows that do not match minutesAtAttenuationFilters (>= 73 dB)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 2, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 73, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 73, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "keep Exposure Windows that match minutesAtAttenuationFilters (< 73 dB)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 2, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 72, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 72, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "drop Exposure Windows that do not match trlFilters (<= 2)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 2, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "keep Exposure Windows that match trlFilters (> 2)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "identify Exposure Window as Low Risk based on normalizedTime (< 15)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 1, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 299 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfExposureWindowsWithLowRisk": 1, + "expNumberOfExposureWindowsWithHighRisk": 0 + }, + { + "description": "identify Exposure Window as High Risk based on normalizedTime (>= 15)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 1, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 1, + "expNumberOfExposureWindowsWithLowRisk": 1, + "expNumberOfExposureWindowsWithHighRisk": 0 + }, + { + "description": "identify the most recent date with Low Risk", + "exposureWindows": [ + { + "ageInDays": 3, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 2, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 4, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 3, + "expAgeOfMostRecentDateWithLowRisk": 2, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "count Exposure Windows with same Date/TRL/CallibrationConfidence only once towards distinct encounters with Low Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "count Exposure Windows with same Date/TRL but different CallibrationConfidence separately towards distinct encounters with Low Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 1, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 2, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "count Exposure Windows with same Date/CallibrationConfidence but different TRL separately towards distinct encounters with Low Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 1, + "reportType": 4, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 2, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "count Exposure Windows with same TRL/CallibrationConfidence but different Date separately towards distinct encounters with Low Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 2, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 2, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "determine High Risk in total if there are sufficient Exposure Windows with a Low Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0 + }, + { + "description": "identify the most recent date with High Risk", + "exposureWindows": [ + { + "ageInDays": 3, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + }, + { + "ageInDays": 2, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + }, + { + "ageInDays": 4, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + } + ], + "expTotalRiskLevel": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": 2, + "expTotalMinimumDistinctEncountersWithHighRisk": 3 + }, + { + "description": "count Exposure Windows with same Date/TRL/CallibrationConfidence only once towards distinct encounters with High Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + }, + { + "ageInDays": 1, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + } + ], + "expTotalRiskLevel": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 1 + }, + { + "description": "count Exposure Windows with same Date/TRL but different CallibrationConfidence separately towards distinct encounters with High Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + }, + { + "ageInDays": 1, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 1, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + } + ], + "expTotalRiskLevel": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 2 + }, + { + "description": "count Exposure Windows with same Date/CallibrationConfidence but different TRL separately towards distinct encounters with High Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + }, + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + } + ], + "expTotalRiskLevel": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 2 + }, + { + "description": "count Exposure Windows with same TRL/CallibrationConfidence but different Date separately towards distinct encounters with High Risk", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + }, + { + "ageInDays": 2, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + } + ], + "expTotalRiskLevel": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 2 + }, + { + "description": "determine High Risk in total if there is at least one Exposure Window with High Risk", + "exposureWindows": [ + { + "ageInDays": 2, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 1, + "reportType": 4, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + }, + { + "typicalAttenuation": 30, + "minAttenuation": 25, + "secondsSinceLastScan": 420 + } + ] + } + ], + "expTotalRiskLevel": 2, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expAgeOfMostRecentDateWithLowRisk": 2, + "expAgeOfMostRecentDateWithHighRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 1 + }, + { + "description": "handle empty set of Exposure Windows", + "exposureWindows": [], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": null + }, + { + "description": "handle empty set of Scan Instances (should never happen)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 2, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 0, + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": null, + "expAgeOfMostRecentDateWithHighRisk": null + }, + { + "description": "handle a typicalAttenuation of zero (should never happen)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 0, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 70, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expNumberOfExposureWindowsWithLowRisk": 1, + "expNumberOfExposureWindowsWithHighRisk": 0 + }, + { + "description": "handle secondsSinceLastScan of zero (should never happen)", + "exposureWindows": [ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 2, + "calibrationConfidence": 0, + "scanInstances": [ + { + "typicalAttenuation": 70, + "minAttenuation": 25, + "secondsSinceLastScan": 0 + }, + { + "typicalAttenuation": 70, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "typicalAttenuation": 70, + "minAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + } + ], + "expTotalRiskLevel": 1, + "expTotalMinimumDistinctEncountersWithLowRisk": 1, + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expAgeOfMostRecentDateWithLowRisk": 1, + "expAgeOfMostRecentDateWithHighRisk": null, + "expNumberOfExposureWindowsWithLowRisk": 1, + "expNumberOfExposureWindowsWithHighRisk": 0 + } + ] +} From a5630bed85c2f20cb81bf43a3a3ce3568876c05f Mon Sep 17 00:00:00 2001 From: BMItter <46747780+BMItter@users.noreply.github.com> Date: Thu, 12 Nov 2020 16:35:34 +0100 Subject: [PATCH 04/47] Enhance enf client (EXPOSUREAPP-3540) (#1584) * Implemented exposure window provider, adjusted ENF Client * Implemented new provideDiagnosisKeys to activate ExposureWindow mode, deprecated old diagnosis related content * Adjusted ENFClientTest * conflict fix - wip * test fix --- .../de/rki/coronawarnapp/nearby/ENFClient.kt | 21 +++++++++++++++- .../de/rki/coronawarnapp/nearby/ENFModule.kt | 7 ++++++ .../DefaultDiagnosisKeyProvider.kt | 25 +++++++++++++++++++ .../DiagnosisKeyProvider.kt | 3 +++ .../diagnosiskeyprovider/SubmissionQuota.kt | 4 +++ .../DefaultExposureWindowProvider.kt | 20 +++++++++++++++ .../exposurewindow/ExposureWindowProvider.kt | 7 ++++++ .../coronawarnapp/util/GoogleAPIVersion.kt | 1 + .../rki/coronawarnapp/nearby/ENFClientTest.kt | 18 +++++++++++++ 9 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/DefaultExposureWindowProvider.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/ExposureWindowProvider.kt diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt index c3de7abd061..38d4f40b1d4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt @@ -4,9 +4,11 @@ package de.rki.coronawarnapp.nearby import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +import de.rki.coronawarnapp.nearby.modules.exposurewindow.ExposureWindowProvider import de.rki.coronawarnapp.nearby.modules.locationless.ScanningSupport import de.rki.coronawarnapp.nearby.modules.tracing.TracingStatus import kotlinx.coroutines.flow.Flow @@ -23,8 +25,10 @@ class ENFClient @Inject constructor( private val diagnosisKeyProvider: DiagnosisKeyProvider, private val tracingStatus: TracingStatus, private val scanningSupport: ScanningSupport, + private val exposureWindowProvider: ExposureWindowProvider, + private val exposureDetectionTracker: ExposureDetectionTracker -) : DiagnosisKeyProvider, TracingStatus, ScanningSupport { +) : DiagnosisKeyProvider, TracingStatus, ScanningSupport, ExposureWindowProvider { // TODO Remove this once we no longer need direct access to the ENF Client, // i.e. in **[InternalExposureNotificationClient]** @@ -51,6 +55,19 @@ class ENFClient @Inject constructor( } } + override suspend fun provideDiagnosisKeys(keyFiles: Collection): Boolean { + Timber.d("asyncProvideDiagnosisKeys(keyFiles=$keyFiles)") + + return if (keyFiles.isEmpty()) { + Timber.d("No key files submitted, returning early.") + true + } else { + Timber.d("Forwarding %d key files to our DiagnosisKeyProvider.", keyFiles.size) + TODO("Call calculationTracker.trackNewCalaculation with an UUID as replacement for token?") + diagnosisKeyProvider.provideDiagnosisKeys(keyFiles) + } + } + override val isLocationLessScanningSupported: Flow get() = scanningSupport.isLocationLessScanningSupported @@ -72,4 +89,6 @@ class ENFClient @Inject constructor( .filter { !it.isCalculating && it.isSuccessful } .maxByOrNull { it.finishedAt ?: Instant.EPOCH } } + + override suspend fun exposureWindows(): List = exposureWindowProvider.exposureWindows() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt index 9d98d5b33bf..ec99ff4e77f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt @@ -9,6 +9,8 @@ import de.rki.coronawarnapp.nearby.modules.detectiontracker.DefaultExposureDetec import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DefaultDiagnosisKeyProvider import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +import de.rki.coronawarnapp.nearby.modules.exposurewindow.DefaultExposureWindowProvider +import de.rki.coronawarnapp.nearby.modules.exposurewindow.ExposureWindowProvider import de.rki.coronawarnapp.nearby.modules.locationless.DefaultScanningSupport import de.rki.coronawarnapp.nearby.modules.locationless.ScanningSupport import de.rki.coronawarnapp.nearby.modules.tracing.DefaultTracingStatus @@ -39,6 +41,11 @@ class ENFModule { fun scanningSupport(scanningSupport: DefaultScanningSupport): ScanningSupport = scanningSupport + @Singleton + @Provides + fun exposureWindowProvider(exposureWindowProvider: DefaultExposureWindowProvider): ExposureWindowProvider = + exposureWindowProvider + @Singleton @Provides fun calculationTracker(exposureDetectionTracker: DefaultExposureDetectionTracker): ExposureDetectionTracker = diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt index 59114d58d4c..a0cbf8b53d1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt @@ -54,6 +54,31 @@ class DefaultDiagnosisKeyProvider @Inject constructor( } } + override suspend fun provideDiagnosisKeys(keyFiles: Collection): Boolean { + if (keyFiles.isEmpty()) { + Timber.d("No key files submitted, returning early.") + return true + } + + if (!googleAPIVersion.isAtLeast(GoogleAPIVersion.V15)) { + // Actually this shouldn't happen + Timber.d("No key files submitted because client uses an old unsupported version") + return false + } + + if (!submissionQuota.consumeQuota(1)) { + Timber.w("No key files submitted because not enough quota available.") + } + + return suspendCoroutine { cont -> + Timber.d("Performing key submission.") + enfClient + .provideDiagnosisKeys(keyFiles.toList()) + .addOnSuccessListener { cont.resume(true) } + .addOnFailureListener { cont.resumeWithException(it) } + } + } + private suspend fun provideKeys( files: Collection, configuration: ExposureConfiguration, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt index accedeed05b..d4a2f00fa1a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt @@ -17,9 +17,12 @@ interface DiagnosisKeyProvider { * @param token * @return */ + @Deprecated("Use provideDiagnosisKeys with only keyFiles as param to activate WindowExposure mode") suspend fun provideDiagnosisKeys( keyFiles: Collection, configuration: ExposureConfiguration?, token: String ): Boolean + + suspend fun provideDiagnosisKeys(keyFiles: Collection): Boolean } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt index d9bd5350698..bf5ff885a0c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt @@ -86,6 +86,10 @@ class SubmissionQuota @Inject constructor( companion object { @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + /** + * This quota changes when using ExposureWindow mode from 20 to 6 per day + * See: https://developers.google.com/android/exposure-notifications/release-notes + */ internal const val DEFAULT_QUOTA = 20 } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/DefaultExposureWindowProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/DefaultExposureWindowProvider.kt new file mode 100644 index 00000000000..5f83c9bba28 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/DefaultExposureWindowProvider.kt @@ -0,0 +1,20 @@ +package de.rki.coronawarnapp.nearby.modules.exposurewindow + +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Singleton +class DefaultExposureWindowProvider @Inject constructor( + private val client: ExposureNotificationClient +) : ExposureWindowProvider { + override suspend fun exposureWindows(): List = suspendCoroutine { cont -> + client.exposureWindows + .addOnSuccessListener { cont.resume(it) } + .addOnFailureListener { cont.resumeWithException(it) } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/ExposureWindowProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/ExposureWindowProvider.kt new file mode 100644 index 00000000000..713715c879f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/ExposureWindowProvider.kt @@ -0,0 +1,7 @@ +package de.rki.coronawarnapp.nearby.modules.exposurewindow + +import com.google.android.gms.nearby.exposurenotification.ExposureWindow + +interface ExposureWindowProvider { + suspend fun exposureWindows(): List +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt index 6af00068b37..8119586bcba 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt @@ -36,5 +36,6 @@ class GoogleAPIVersion @Inject constructor() { companion object { private const val GOOGLE_API_VERSION_FIELD_LENGTH = 8 const val V16 = 16000000L + const val V15 = 15000000L } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt index 4880b66aa8e..1f242c4c2c3 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt @@ -2,9 +2,11 @@ package de.rki.coronawarnapp.nearby import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +import de.rki.coronawarnapp.nearby.modules.exposurewindow.ExposureWindowProvider import de.rki.coronawarnapp.nearby.modules.locationless.ScanningSupport import de.rki.coronawarnapp.nearby.modules.tracing.TracingStatus import io.kotest.matchers.shouldBe @@ -37,6 +39,7 @@ class ENFClientTest : BaseTest() { @MockK lateinit var diagnosisKeyProvider: DiagnosisKeyProvider @MockK lateinit var tracingStatus: TracingStatus @MockK lateinit var scanningSupport: ScanningSupport + @MockK lateinit var exposureWindowProvider: ExposureWindowProvider @MockK lateinit var exposureDetectionTracker: ExposureDetectionTracker @BeforeEach @@ -56,6 +59,8 @@ class ENFClientTest : BaseTest() { diagnosisKeyProvider = diagnosisKeyProvider, tracingStatus = tracingStatus, scanningSupport = scanningSupport, + + exposureWindowProvider = exposureWindowProvider, exposureDetectionTracker = exposureDetectionTracker ) @@ -265,4 +270,17 @@ class ENFClientTest : BaseTest() { createClient().lastSuccessfulTrackedExposureDetection().first()!!.identifier shouldBe "1" } } + + @Test + fun `exposure windows check is forwarded to the right module`() = runBlocking { + val exposureWindowList = emptyList() + coEvery { exposureWindowProvider.exposureWindows() } returns exposureWindowList + + val client = createClient() + client.exposureWindows() shouldBe exposureWindowList + + coVerify(exactly = 1) { + exposureWindowProvider.exposureWindows() + } + } } From dcf361ab275261758e84d502c803635b6963b82c Mon Sep 17 00:00:00 2001 From: BMItter <46747780+BMItter@users.noreply.github.com> Date: Mon, 16 Nov 2020 13:06:35 +0100 Subject: [PATCH 05/47] ENFClient v2 - ExposureDetectionTracker without identifier (EXPOSUREAPP-3540) (#1605) * Implemented the opportunity to use ExposureDetectionTracker without identifier * Old provideDiagnosisKeys = NO-OP * clean * maxLength clean * refactoring and extension function * adjusted wording * Throw UnsupportedOperationException in ENFClient in case * ktlint clean * fix tests for now * commented out Exception temporarily * adjusted enfTest -wip, throw unsupportedOperationException --- .../de/rki/coronawarnapp/nearby/ENFClient.kt | 19 +---- .../DefaultExposureDetectionTracker.kt | 77 +++++++++++++------ .../ExposureDetectionTracker.kt | 2 +- .../receiver/ExposureStateUpdateReceiver.kt | 19 ++--- .../rki/coronawarnapp/nearby/ENFClientTest.kt | 18 ++--- .../windows/ExposureWindowsCalculationTest.kt | 3 +- 6 files changed, 73 insertions(+), 65 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt index 38d4f40b1d4..9e2b23d155e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.map import org.joda.time.Instant import timber.log.Timber import java.io.File +import java.util.UUID import javax.inject.Inject import javax.inject.Singleton @@ -26,7 +27,6 @@ class ENFClient @Inject constructor( private val tracingStatus: TracingStatus, private val scanningSupport: ScanningSupport, private val exposureWindowProvider: ExposureWindowProvider, - private val exposureDetectionTracker: ExposureDetectionTracker ) : DiagnosisKeyProvider, TracingStatus, ScanningSupport, ExposureWindowProvider { @@ -40,19 +40,8 @@ class ENFClient @Inject constructor( configuration: ExposureConfiguration?, token: String ): Boolean { - Timber.d( - "asyncProvideDiagnosisKeys(keyFiles=%s, configuration=%s, token=%s)", - keyFiles, configuration, token - ) - - return if (keyFiles.isEmpty()) { - Timber.d("No key files submitted, returning early.") - true - } else { - Timber.d("Forwarding %d key files to our DiagnosisKeyProvider.", keyFiles.size) - exposureDetectionTracker.trackNewExposureDetection(token) - diagnosisKeyProvider.provideDiagnosisKeys(keyFiles, configuration, token) - } + // TODO uncomment Exception later, after every subtask has joined (fun will probably be removed) + throw UnsupportedOperationException("Use provideDiagnosisKeys without token and configuration!") } override suspend fun provideDiagnosisKeys(keyFiles: Collection): Boolean { @@ -63,7 +52,7 @@ class ENFClient @Inject constructor( true } else { Timber.d("Forwarding %d key files to our DiagnosisKeyProvider.", keyFiles.size) - TODO("Call calculationTracker.trackNewCalaculation with an UUID as replacement for token?") + exposureDetectionTracker.trackNewExposureDetection(UUID.randomUUID().toString()) diagnosisKeyProvider.provideDiagnosisKeys(keyFiles) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/DefaultExposureDetectionTracker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/DefaultExposureDetectionTracker.kt index c5e4b8073de..2c55673ffcd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/DefaultExposureDetectionTracker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/DefaultExposureDetectionTracker.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.plus import org.joda.time.Duration import timber.log.Timber +import java.util.UUID import javax.inject.Inject import javax.inject.Singleton import kotlin.math.min @@ -63,7 +64,7 @@ class DefaultExposureDetectionTracker @Inject constructor( } } - delay(TIMEOUT_CHECK_INTERVALL.millis) + delay(TIMEOUT_CHECK_INTERVAL.millis) } }.launchIn(scope + dispatcherProvider.Default) } @@ -93,34 +94,17 @@ class DefaultExposureDetectionTracker @Inject constructor( } } - override fun finishExposureDetection(identifier: String, result: Result) { + override fun finishExposureDetection(identifier: String?, result: Result) { Timber.i("finishExposureDetection(token=%s, result=%s)", identifier, result) detectionStates.updateSafely { mutate { - val existing = this[identifier] - if (existing != null) { - if (existing.result == Result.TIMEOUT) { - Timber.w("Detection is late, already hit timeout, still updating.") - } else if (existing.result != null) { - Timber.e("Duplicate callback. Result is already set for detection!") - } - this[identifier] = existing.copy( - result = result, - finishedAt = timeStamper.nowUTC - ) + if (identifier == null) { + val id = this.findUnfinishedOrCreateIdentifier() + finishDetection(id, result) } else { - Timber.e( - "Unknown detection finished (token=%s, result=%s)", - identifier, - result - ) - this[identifier] = TrackedExposureDetection( - identifier = identifier, - result = result, - startedAt = timeStamper.nowUTC, - finishedAt = timeStamper.nowUTC - ) + finishDetection(identifier, result) } + val toKeep = entries .sortedByDescending { it.value.startedAt } // Keep newest .subList(0, min(entries.size, MAX_ENTRY_SIZE)) @@ -134,9 +118,52 @@ class DefaultExposureDetectionTracker @Inject constructor( } } + private fun Map.findUnfinishedOrCreateIdentifier(): String { + val newestUnfinishedDetection = this + .map { it.value } + .filter { it.finishedAt == null } + .maxByOrNull { it.startedAt.millis } + + return if (newestUnfinishedDetection != null) { + Timber.d("findUnfinishedOrCreateIdentifier(): Found unfinished detection, return identifier") + newestUnfinishedDetection.identifier + } else { + Timber.d("findUnfinishedOrCreateIdentifier(): No unfinished detection found, create identifier") + UUID.randomUUID().toString() + } + } + + private fun MutableMap.finishDetection(identifier: String, result: Result) { + Timber.i("finishDetection(token=%s, result=%s)", identifier, result) + val existing = this[identifier] + if (existing != null) { + if (existing.result == Result.TIMEOUT) { + Timber.w("Detection is late, already hit timeout, still updating.") + } else if (existing.result != null) { + Timber.e("Duplicate callback. Result is already set for detection!") + } + this[identifier] = existing.copy( + result = result, + finishedAt = timeStamper.nowUTC + ) + } else { + Timber.e( + "Unknown detection finished (token=%s, result=%s)", + identifier, + result + ) + this[identifier] = TrackedExposureDetection( + identifier = identifier, + result = result, + startedAt = timeStamper.nowUTC, + finishedAt = timeStamper.nowUTC + ) + } + } + companion object { private const val TAG = "DefaultExposureDetectionTracker" private const val MAX_ENTRY_SIZE = 5 - private val TIMEOUT_CHECK_INTERVALL = Duration.standardMinutes(3) + private val TIMEOUT_CHECK_INTERVAL = Duration.standardMinutes(3) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTracker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTracker.kt index 4d9bcf0c6e3..1f0740acda6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTracker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTracker.kt @@ -7,5 +7,5 @@ interface ExposureDetectionTracker { fun trackNewExposureDetection(identifier: String) - fun finishExposureDetection(identifier: String, result: TrackedExposureDetection.Result) + fun finishExposureDetection(identifier: String? = null, result: TrackedExposureDetection.Result) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt index dab0da7f05f..d58137f5eec 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt @@ -11,7 +11,6 @@ import com.google.android.gms.nearby.exposurenotification.ExposureNotificationCl import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient.EXTRA_TOKEN import dagger.android.AndroidInjection import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL -import de.rki.coronawarnapp.exception.NoTokenException import de.rki.coronawarnapp.exception.UnknownBroadcastException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.ExposureStateUpdateWorker @@ -55,9 +54,10 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() { val async = goAsync() scope.launch(context = dispatcherProvider.Default) { try { + val token = intent.getStringExtra(EXTRA_TOKEN) when (action) { - ACTION_EXPOSURE_STATE_UPDATED -> processStateUpdates(intent) - ACTION_EXPOSURE_NOT_FOUND -> processNotFound(intent) + ACTION_EXPOSURE_STATE_UPDATED -> processStateUpdates(token) + ACTION_EXPOSURE_NOT_FOUND -> processNotFound(token) else -> throw UnknownBroadcastException(action) } } catch (e: Exception) { @@ -69,13 +69,12 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() { } } - private fun processStateUpdates(intent: Intent) { + private fun processStateUpdates(token: String?) { Timber.tag(TAG).i("Processing ACTION_EXPOSURE_STATE_UPDATED") val workManager = WorkManager.getInstance(context) - val token = intent.requireToken() - + // TODO("Remove token from ExposureStateUpdateWorker") val data = Data .Builder() .putString(EXTRA_TOKEN, token) @@ -93,21 +92,15 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() { ) } - private fun processNotFound(intent: Intent) { + private fun processNotFound(token: String?) { Timber.tag(TAG).i("Processing ACTION_EXPOSURE_NOT_FOUND") - val token = intent.requireToken() - exposureDetectionTracker.finishExposureDetection( token, TrackedExposureDetection.Result.NO_MATCHES ) } - private fun Intent.requireToken(): String = getStringExtra(EXTRA_TOKEN).also { - Timber.tag(TAG).v("Extracted token: %s", it) - } ?: throw NoTokenException(IllegalArgumentException("no token was found in the intent")) - companion object { private val TAG: String? = ExposureStateUpdateReceiver::class.simpleName } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt index 1f242c4c2c3..35a4efbfe52 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt @@ -77,21 +77,19 @@ class ENFClientTest : BaseTest() { val configuration = mockk() val token = "123" - coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns true + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true runBlocking { - client.provideDiagnosisKeys(keyFiles, configuration, token) shouldBe true + client.provideDiagnosisKeys(keyFiles) shouldBe true } - coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns false + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns false runBlocking { - client.provideDiagnosisKeys(keyFiles, configuration, token) shouldBe false + client.provideDiagnosisKeys(keyFiles) shouldBe false } coVerify(exactly = 2) { diagnosisKeyProvider.provideDiagnosisKeys( - keyFiles, - configuration, - token + keyFiles ) } } @@ -103,13 +101,13 @@ class ENFClientTest : BaseTest() { val configuration = mockk() val token = "123" - coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns true + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true runBlocking { - client.provideDiagnosisKeys(keyFiles, configuration, token) shouldBe true + client.provideDiagnosisKeys(keyFiles) shouldBe true } coVerify(exactly = 0) { - diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) + diagnosisKeyProvider.provideDiagnosisKeys(any()) } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt index 3dab010d2aa..41c26ebc228 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -297,7 +297,8 @@ class ExposureWindowsCalculationTest : BaseTest() { serverTime = Instant.now(), localOffset = Duration.ZERO, mappedConfig = configData, - isFallback = false + identifier = "soup", + configType = ConfigData.Type.FROM_SERVER ) val attenuationFilters = mutableListOf() From fdfe57c11687c80a9ad06178a856c30f8201c894 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Mon, 16 Nov 2020 14:20:03 +0100 Subject: [PATCH 06/47] Initial switch to new app config version (EXPOSUREAPP-3414) (#1602) * Initial switch to new app config version (no tests/lint) * Fixed some unit tests, not all are working yet * Fixed ConfigParserTest * ktlint fixes * ktlint * ExposureWindows to map is now beautiful Signed-off-by: Kolya Opahle Co-authored-by: BMItter --- .../test/api/ui/TestForAPIFragment.kt | 4 +- ...iskLevelCalculationFragmentCWAViewModel.kt | 111 ++++-------------- .../appconfig/AppConfigModule.kt | 13 +- .../rki/coronawarnapp/appconfig/CWAConfig.kt | 9 +- .../appconfig/ExposureDetectionConfig.kt | 4 +- .../appconfig/RiskCalculationConfig.kt | 16 --- .../appconfig/download/AppConfigApiV1.kt | 14 --- .../appconfig/download/AppConfigApiV2.kt | 11 ++ .../appconfig/download/AppConfigServer.kt | 7 +- .../appconfig/mapping/CWAConfigMapper.kt | 19 +-- .../appconfig/mapping/ConfigMapper.kt | 4 +- .../appconfig/mapping/ConfigMapping.kt | 6 +- .../appconfig/mapping/ConfigParser.kt | 15 +-- .../appconfig/mapping/DefaultConfigMapping.kt | 7 +- .../mapping/ExposureDetectionConfigMapper.kt | 62 +--------- .../mapping/KeyDownloadParametersMapper.kt | 8 +- .../mapping/RiskCalculationConfigMapper.kt | 26 ---- .../download/DownloadDiagnosisKeysTask.kt | 4 +- .../coronawarnapp/risk/DefaultRiskLevels.kt | 96 ++------------- .../rki/coronawarnapp/risk/RiskLevelTask.kt | 25 +--- .../de/rki/coronawarnapp/risk/RiskLevels.kt | 10 +- .../rki/coronawarnapp/update/UpdateChecker.kt | 24 +--- .../coronawarnapp/update/VersionComparator.kt | 29 +---- .../appconfig/download/AppConfigApiTest.kt | 22 ++-- .../appconfig/download/AppConfigServerTest.kt | 17 ++- .../appconfig/mapping/CWAConfigMapperTest.kt | 17 +-- .../appconfig/mapping/ConfigParserTest.kt | 26 ++-- .../mapping/DownloadConfigMapperTest.kt | 12 +- .../ExposureDetectionConfigMapperTest.kt | 30 ++--- .../RiskCalculationConfigMapperTest.kt | 22 ---- .../rki/coronawarnapp/risk/RiskLevelsTest.kt | 107 ----------------- .../update/VersionComparatorTest.kt | 18 +-- ...evelCalculationFragmentCWAViewModelTest.kt | 5 +- 33 files changed, 170 insertions(+), 630 deletions(-) delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/RiskCalculationConfig.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV1.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV2.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/RiskCalculationConfigMapper.kt delete mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/RiskCalculationConfigMapperTest.kt diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt index 694615a9ae2..1f9d0818c3d 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt @@ -310,9 +310,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), try { // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API enfClient.provideDiagnosisKeys( - googleFileList, - AppInjector.component.appConfigProvider.getAppConfig().exposureDetectionConfiguration, - token!! + googleFileList ) showToast("Provided ${appleKeyList.size} keys to Google API with token $token") } catch (e: Exception) { diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index d0a85e7f0f7..e052aec8017 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -5,16 +5,14 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import com.google.android.gms.nearby.exposurenotification.ExposureInformation import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.appconfig.RiskCalculationConfig +import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.ENFClient -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.risk.RiskLevel import de.rki.coronawarnapp.risk.RiskLevelTask import de.rki.coronawarnapp.risk.RiskLevels @@ -30,7 +28,6 @@ import de.rki.coronawarnapp.ui.tracing.card.TracingCardStateProvider import de.rki.coronawarnapp.util.KeyFileHelper import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.di.AppContext -import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.security.SecurityHelper import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel @@ -43,9 +40,6 @@ import timber.log.Timber import java.io.File import java.util.UUID import java.util.concurrent.TimeUnit -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( @Assisted private val handle: SavedStateHandle, @@ -56,6 +50,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( private val riskLevels: RiskLevels, private val taskController: TaskController, private val keyCacheRepository: KeyCacheRepository, + private val appConfigProvider: AppConfigProvider, tracingCardStateProvider: TracingCardStateProvider ) : CWAViewModel( dispatcherProvider = dispatcherProvider @@ -126,22 +121,19 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( try { var workState = riskScoreState.value!! - val googleToken = LocalData.googleApiToken() ?: UUID.randomUUID().toString() - val exposureSummary = - InternalExposureNotificationClient.asyncGetExposureSummary(googleToken) + val exposureWindows = enfClient.exposureWindows() - val expDetectConfig: RiskCalculationConfig = - AppInjector.component.appConfigProvider.getAppConfig() + val riskResultsPerWindow = + exposureWindows.mapNotNull { window -> + riskLevels.calculateRisk(window)?.let { window to it } + }.toMap() - val riskLevelScore = riskLevels.calculateRiskScore( - expDetectConfig.attenuationDuration, - exposureSummary - ) + val aggregatedResult = riskLevels.aggregateResults(riskResultsPerWindow) val riskAsString = "Level: ${RiskLevelRepository.getLastCalculatedScore()}\n" + "Last successful Level: " + "${LocalData.lastSuccessfullyCalculatedRiskLevel()}\n" + - "Calculated Score: ${riskLevelScore}\n" + + "Calculated Score: ${aggregatedResult}\n" + "Last Time Server Fetch: ${LocalData.lastTimeDiagnosisKeysFromServerFetch()}\n" + "Tracing Duration: " + "${TimeUnit.MILLISECONDS.toDays(TimeVariables.getTimeActiveTracingDuration())} days \n" + @@ -151,72 +143,27 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( workState = workState.copy(riskScoreMsg = riskAsString) - val lowClass = - expDetectConfig.riskScoreClasses.riskClassesList?.find { low -> low.label == "LOW" } - val highClass = - expDetectConfig.riskScoreClasses.riskClassesList?.find { high -> high.label == "HIGH" } + val appConfig = appConfigProvider.getAppConfig() val configAsString = - "Attenuation Weight Low: ${expDetectConfig.attenuationDuration.weights?.low}\n" + - "Attenuation Weight Mid: ${expDetectConfig.attenuationDuration.weights?.mid}\n" + - "Attenuation Weight High: ${expDetectConfig.attenuationDuration.weights?.high}\n\n" + - "Attenuation Offset: ${expDetectConfig.attenuationDuration.defaultBucketOffset}\n" + - "Attenuation Normalization: " + - "${expDetectConfig.attenuationDuration.riskScoreNormalizationDivisor}\n\n" + - "Risk Score Low Class: ${lowClass?.min ?: 0} - ${lowClass?.max ?: 0}\n" + - "Risk Score High Class: ${highClass?.min ?: 0} - ${highClass?.max ?: 0}" - + "Transmission RiskLevel Multiplier: ${appConfig.transmissionRiskLevelMultiplier}\n" + + "Minutes At Attenuation Filters: ${appConfig.minutesAtAttenuationFilters}\n" + + "Minutes At Attenuation Weights: ${appConfig.minutesAtAttenuationWeights}" + + "Transmission RiskLevel Encoding: ${appConfig.transmissionRiskLevelEncoding}" + + "Transmission RiskLevel Filters: ${appConfig.transmissionRiskLevelFilters}" + + "Normalized Time Per Exposure Window To RiskLevel Mapping: ${appConfig.normalizedTimePerExposureWindowToRiskLevelMapping}" + + "Normalized Time Per Day To RiskLevel Mapping List: ${appConfig.normalizedTimePerDayToRiskLevelMappingList}" workState = workState.copy(backendParameters = configAsString) val summaryAsString = - "Days Since Last Exposure: ${exposureSummary.daysSinceLastExposure}\n" + - "Matched Key Count: ${exposureSummary.matchedKeyCount}\n" + - "Maximum Risk Score: ${exposureSummary.maximumRiskScore}\n" + - "Attenuation Durations: [${ - exposureSummary.attenuationDurationsInMinutes?.get( - 0 - ) - }," + - "${exposureSummary.attenuationDurationsInMinutes?.get(1)}," + - "${exposureSummary.attenuationDurationsInMinutes?.get(2)}]\n" + - "Summation Risk Score: ${exposureSummary.summationRiskScore}" + "Total RiskLevel: ${aggregatedResult.totalRiskLevel}" + + "Total Minimum Distinct Encounters With High Risk: ${aggregatedResult.totalMinimumDistinctEncountersWithHighRisk}" + + "Total Minimum Distinct Encounters With Low Risk: ${aggregatedResult.totalMinimumDistinctEncountersWithLowRisk}" + + "Most Recent Date With High Risk: ${aggregatedResult.mostRecentDateWithHighRisk}" + + "Most Recent Date With Low Risk: ${aggregatedResult.mostRecentDateWithLowRisk}" workState = workState.copy(exposureSummary = summaryAsString) - val maxRisk = exposureSummary.maximumRiskScore - val atWeights = expDetectConfig.attenuationDuration.weights - val attenuationDurationInMin = - exposureSummary.attenuationDurationsInMinutes - val attenuationConfig = expDetectConfig.attenuationDuration - val formulaString = - "($maxRisk / ${attenuationConfig.riskScoreNormalizationDivisor}) * " + - "(${attenuationDurationInMin?.get(0)} * ${atWeights?.low} " + - "+ ${attenuationDurationInMin?.get(1)} * ${atWeights?.mid} " + - "+ ${attenuationDurationInMin?.get(2)} * ${atWeights?.high} " + - "+ ${attenuationConfig.defaultBucketOffset})" - - workState = workState.copy(formula = formulaString) - - val token = LocalData.googleApiToken() - if (token != null) { - val exposureInformation = asyncGetExposureInformation(token) - - var infoString = "" - exposureInformation.forEach { - infoString += "Attenuation duration in min.: " + - "[${it.attenuationDurationsInMinutes?.get(0)}, " + - "${it.attenuationDurationsInMinutes?.get(1)}," + - "${it.attenuationDurationsInMinutes?.get(2)}]\n" + - "Attenuation value: ${it.attenuationValue}\n" + - "Duration in min.: ${it.durationMinutes}\n" + - "Risk Score: ${it.totalRiskScore}\n" + - "Transmission Risk Level: ${it.transmissionRiskLevel}\n" + - "Date Millis Since Epoch: ${it.dateMillisSinceEpoch}\n\n" - } - - workState = workState.copy(exposureInfo = infoString) - } - riskScoreState.postValue(workState) } catch (e: Exception) { e.report(ExceptionCategory.EXPOSURENOTIFICATION) @@ -224,16 +171,6 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( } } - private suspend fun asyncGetExposureInformation(token: String): List = - suspendCoroutine { cont -> - enfClient.internalClient.getExposureInformation(token) - .addOnSuccessListener { - cont.resume(it) - }.addOnFailureListener { - cont.resumeWithException(it) - } - } - data class DiagnosisKeyProvidedEvent( val keyCount: Int, val token: String @@ -270,9 +207,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( try { // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API enfClient.provideDiagnosisKeys( - googleFileList, - AppInjector.component.appConfigProvider.getAppConfig().exposureDetectionConfiguration, - token + googleFileList ) apiKeysProvidedEvent.postValue( DiagnosisKeyProvidedEvent( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt index b97d54d2306..042fb6f35d4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt @@ -3,13 +3,12 @@ package de.rki.coronawarnapp.appconfig import android.content.Context import dagger.Module import dagger.Provides -import de.rki.coronawarnapp.appconfig.download.AppConfigApiV1 +import de.rki.coronawarnapp.appconfig.download.AppConfigApiV2 import de.rki.coronawarnapp.appconfig.download.AppConfigHttpCache import de.rki.coronawarnapp.appconfig.mapping.CWAConfigMapper import de.rki.coronawarnapp.appconfig.mapping.ExposureDetectionConfigMapper import de.rki.coronawarnapp.appconfig.mapping.ExposureWindowRiskCalculationConfigMapper import de.rki.coronawarnapp.appconfig.mapping.KeyDownloadParametersMapper -import de.rki.coronawarnapp.appconfig.mapping.RiskCalculationConfigMapper import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient import de.rki.coronawarnapp.environment.download.DownloadCDNServerUrl import de.rki.coronawarnapp.util.di.AppContext @@ -43,7 +42,7 @@ class AppConfigModule { @DownloadCDNServerUrl url: String, gsonConverterFactory: GsonConverterFactory, @AppConfigHttpCache cache: Cache - ): AppConfigApiV1 { + ): AppConfigApiV2 { val cachingClient = client.newBuilder().apply { cache(cache) @@ -58,7 +57,7 @@ class AppConfigModule { .baseUrl(url) .addConverterFactory(gsonConverterFactory) .build() - .create(AppConfigApiV1::class.java) + .create(AppConfigApiV2::class.java) } @Provides @@ -69,13 +68,9 @@ class AppConfigModule { fun downloadMapper(mapper: KeyDownloadParametersMapper): KeyDownloadConfig.Mapper = mapper @Provides - fun exposurMapper(mapper: ExposureDetectionConfigMapper): + fun exposureMapper(mapper: ExposureDetectionConfigMapper): ExposureDetectionConfig.Mapper = mapper - @Provides - fun riskMapper(mapper: RiskCalculationConfigMapper): - RiskCalculationConfig.Mapper = mapper - @Provides fun windowRiskMapper(mapper: ExposureWindowRiskCalculationConfigMapper): ExposureWindowRiskCalculationConfig.Mapper = mapper diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/CWAConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/CWAConfig.kt index 0a11bf82211..89532fb14eb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/CWAConfig.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/CWAConfig.kt @@ -1,16 +1,17 @@ package de.rki.coronawarnapp.appconfig import de.rki.coronawarnapp.appconfig.mapping.ConfigMapper -import de.rki.coronawarnapp.server.protocols.internal.AppFeaturesOuterClass -import de.rki.coronawarnapp.server.protocols.internal.AppVersionConfig +import de.rki.coronawarnapp.server.protocols.internal.v2.AppFeaturesOuterClass interface CWAConfig { - val appVersion: AppVersionConfig.ApplicationVersionConfiguration + val latestVersionCode: Long + + val minVersionCode: Long val supportedCountries: List - val appFeatureus: AppFeaturesOuterClass.AppFeatures + val appFeatures: AppFeaturesOuterClass.AppFeatures interface Mapper : ConfigMapper } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureDetectionConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureDetectionConfig.kt index be47c2f17b4..2f6e3445eab 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureDetectionConfig.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureDetectionConfig.kt @@ -1,8 +1,7 @@ package de.rki.coronawarnapp.appconfig -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import de.rki.coronawarnapp.appconfig.mapping.ConfigMapper -import de.rki.coronawarnapp.server.protocols.internal.ExposureDetectionParameters +import de.rki.coronawarnapp.server.protocols.internal.v2.ExposureDetectionParameters import org.joda.time.Duration interface ExposureDetectionConfig { @@ -11,7 +10,6 @@ interface ExposureDetectionConfig { val minTimeBetweenDetections: Duration val overallDetectionTimeout: Duration - val exposureDetectionConfiguration: ExposureConfiguration val exposureDetectionParameters: ExposureDetectionParameters.ExposureDetectionParametersAndroid interface Mapper : ConfigMapper diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/RiskCalculationConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/RiskCalculationConfig.kt deleted file mode 100644 index 2c19c0be637..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/RiskCalculationConfig.kt +++ /dev/null @@ -1,16 +0,0 @@ -package de.rki.coronawarnapp.appconfig - -import de.rki.coronawarnapp.appconfig.mapping.ConfigMapper -import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass -import de.rki.coronawarnapp.server.protocols.internal.RiskScoreClassificationOuterClass - -interface RiskCalculationConfig { - - val minRiskScore: Int - - val attenuationDuration: AttenuationDurationOuterClass.AttenuationDuration - - val riskScoreClasses: RiskScoreClassificationOuterClass.RiskScoreClassification - - interface Mapper : ConfigMapper -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV1.kt deleted file mode 100644 index 0c3f61077cc..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV1.kt +++ /dev/null @@ -1,14 +0,0 @@ -package de.rki.coronawarnapp.appconfig.download - -import okhttp3.ResponseBody -import retrofit2.Response -import retrofit2.http.GET -import retrofit2.http.Path - -interface AppConfigApiV1 { - - @GET("/version/v1/configuration/country/{country}/app_config") - suspend fun getApplicationConfiguration( - @Path("country") country: String - ): Response -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV2.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV2.kt new file mode 100644 index 00000000000..5d1c0f2c3d0 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiV2.kt @@ -0,0 +1,11 @@ +package de.rki.coronawarnapp.appconfig.download + +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.http.GET + +interface AppConfigApiV2 { + + @GET("/version/v1/app_config_android") + suspend fun getApplicationConfiguration(): Response +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigServer.kt index 7d174ac1525..44cbd3e3aec 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/AppConfigServer.kt @@ -2,8 +2,6 @@ package de.rki.coronawarnapp.appconfig.download import dagger.Lazy import dagger.Reusable -import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode -import de.rki.coronawarnapp.environment.download.DownloadCDNHomeCountry import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.ZipHelper.readIntoMap import de.rki.coronawarnapp.util.ZipHelper.unzip @@ -21,17 +19,16 @@ import javax.inject.Inject @Reusable class AppConfigServer @Inject constructor( - private val api: Lazy, + private val api: Lazy, private val verificationKeys: VerificationKeys, private val timeStamper: TimeStamper, - @DownloadCDNHomeCountry private val homeCountry: LocationCode, @AppConfigHttpCache private val cache: Cache ) { internal suspend fun downloadAppConfig(): ConfigDownload { Timber.tag(TAG).d("Fetching app config.") - val response = api.get().getApplicationConfiguration(homeCountry.identifier) + val response = api.get().getApplicationConfiguration() if (!response.isSuccessful) throw HttpException(response) val rawConfig = with( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/CWAConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/CWAConfigMapper.kt index 8d78dddcdb0..8c2e9502ffe 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/CWAConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/CWAConfigMapper.kt @@ -3,24 +3,24 @@ package de.rki.coronawarnapp.appconfig.mapping import androidx.annotation.VisibleForTesting import dagger.Reusable import de.rki.coronawarnapp.appconfig.CWAConfig -import de.rki.coronawarnapp.server.protocols.internal.AppConfig -import de.rki.coronawarnapp.server.protocols.internal.AppFeaturesOuterClass -import de.rki.coronawarnapp.server.protocols.internal.AppVersionConfig +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.AppFeaturesOuterClass import timber.log.Timber import javax.inject.Inject @Reusable class CWAConfigMapper @Inject constructor() : CWAConfig.Mapper { - override fun map(rawConfig: AppConfig.ApplicationConfiguration): CWAConfig { + override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): CWAConfig { return CWAConfigContainer( - appVersion = rawConfig.appVersion, + latestVersionCode = rawConfig.latestVersionCode, + minVersionCode = rawConfig.minVersionCode, supportedCountries = rawConfig.getMappedSupportedCountries(), - appFeatureus = rawConfig.appFeatures + appFeatures = rawConfig.appFeatures ) } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal fun AppConfig.ApplicationConfiguration.getMappedSupportedCountries(): List = + internal fun AppConfigAndroid.ApplicationConfigurationAndroid.getMappedSupportedCountries(): List = when { supportedCountriesList == null -> emptyList() supportedCountriesList.size == 1 && !VALID_CC.matches(supportedCountriesList.single()) -> { @@ -31,9 +31,10 @@ class CWAConfigMapper @Inject constructor() : CWAConfig.Mapper { } data class CWAConfigContainer( - override val appVersion: AppVersionConfig.ApplicationVersionConfiguration, + override val latestVersionCode: Long, + override val minVersionCode: Long, override val supportedCountries: List, - override val appFeatureus: AppFeaturesOuterClass.AppFeatures + override val appFeatures: AppFeaturesOuterClass.AppFeatures ) : CWAConfig companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapper.kt index 58c4b88b2f7..1c822d0f2a7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapper.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.appconfig.mapping -import de.rki.coronawarnapp.server.protocols.internal.AppConfig +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid interface ConfigMapper { - fun map(rawConfig: AppConfig.ApplicationConfiguration): T + fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): T } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt index 1e9ccb2d404..08a358c7a40 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt @@ -4,16 +4,14 @@ import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.KeyDownloadConfig -import de.rki.coronawarnapp.appconfig.RiskCalculationConfig -import de.rki.coronawarnapp.server.protocols.internal.AppConfig +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid interface ConfigMapping : CWAConfig, KeyDownloadConfig, ExposureDetectionConfig, - RiskCalculationConfig, ExposureWindowRiskCalculationConfig { @Deprecated("Try to access a more specific config type, avoid the RAW variant.") - val rawConfig: AppConfig.ApplicationConfiguration + val rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt index 41988550a2c..54ccf2419a4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt @@ -5,8 +5,6 @@ import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.KeyDownloadConfig -import de.rki.coronawarnapp.appconfig.RiskCalculationConfig -import de.rki.coronawarnapp.server.protocols.internal.AppConfig import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import timber.log.Timber import javax.inject.Inject @@ -16,24 +14,17 @@ class ConfigParser @Inject constructor( private val cwaConfigMapper: CWAConfig.Mapper, private val keyDownloadConfigMapper: KeyDownloadConfig.Mapper, private val exposureDetectionConfigMapper: ExposureDetectionConfig.Mapper, - private val riskCalculationConfigMapper: RiskCalculationConfig.Mapper, private val exposureWindowRiskCalculationConfigMapper: ExposureWindowRiskCalculationConfig.Mapper ) { fun parse(configBytes: ByteArray): ConfigMapping = try { - // TODO replace with actual v2 config - val dummyConfig = AppConfigAndroid - .ApplicationConfigurationAndroid - .newBuilder() - .build() parseRawArray(configBytes).let { DefaultConfigMapping( rawConfig = it, cwaConfig = cwaConfigMapper.map(it), keyDownloadConfig = keyDownloadConfigMapper.map(it), exposureDetectionConfig = exposureDetectionConfigMapper.map(it), - riskCalculationConfig = riskCalculationConfigMapper.map(it), - exposureWindowRiskCalculationConfig = exposureWindowRiskCalculationConfigMapper.map(dummyConfig) + exposureWindowRiskCalculationConfig = exposureWindowRiskCalculationConfigMapper.map(it) ) } } catch (e: Exception) { @@ -41,8 +32,8 @@ class ConfigParser @Inject constructor( throw e } - private fun parseRawArray(configBytes: ByteArray): AppConfig.ApplicationConfiguration { + private fun parseRawArray(configBytes: ByteArray): AppConfigAndroid.ApplicationConfigurationAndroid { Timber.v("Parsing config (size=%dB)", configBytes.size) - return AppConfig.ApplicationConfiguration.parseFrom(configBytes) + return AppConfigAndroid.ApplicationConfigurationAndroid.parseFrom(configBytes) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt index 78bacc12c69..81643e178e8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt @@ -4,19 +4,16 @@ import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.KeyDownloadConfig -import de.rki.coronawarnapp.appconfig.RiskCalculationConfig -import de.rki.coronawarnapp.server.protocols.internal.AppConfig +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid data class DefaultConfigMapping( - override val rawConfig: AppConfig.ApplicationConfiguration, + override val rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid, val cwaConfig: CWAConfig, val keyDownloadConfig: KeyDownloadConfig, val exposureDetectionConfig: ExposureDetectionConfig, - val riskCalculationConfig: RiskCalculationConfig, val exposureWindowRiskCalculationConfig: ExposureWindowRiskCalculationConfig ) : ConfigMapping, CWAConfig by cwaConfig, KeyDownloadConfig by keyDownloadConfig, ExposureDetectionConfig by exposureDetectionConfig, - RiskCalculationConfig by riskCalculationConfig, ExposureWindowRiskCalculationConfig by exposureWindowRiskCalculationConfig diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapper.kt index 92473af001e..81e7522ce5b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapper.kt @@ -1,20 +1,18 @@ package de.rki.coronawarnapp.appconfig.mapping import androidx.annotation.VisibleForTesting -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import dagger.Reusable import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig -import de.rki.coronawarnapp.server.protocols.internal.AppConfig -import de.rki.coronawarnapp.server.protocols.internal.ExposureDetectionParameters.ExposureDetectionParametersAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.ExposureDetectionParameters.ExposureDetectionParametersAndroid import org.joda.time.Duration import javax.inject.Inject @Reusable class ExposureDetectionConfigMapper @Inject constructor() : ExposureDetectionConfig.Mapper { - override fun map(rawConfig: AppConfig.ApplicationConfiguration): ExposureDetectionConfig { - val exposureParams = rawConfig.androidExposureDetectionParameters + override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): ExposureDetectionConfig { + val exposureParams = rawConfig.exposureDetectionParameters return ExposureDetectionConfigContainer( - exposureDetectionConfiguration = rawConfig.mapRiskScoreToExposureConfiguration(), exposureDetectionParameters = exposureParams, maxExposureDetectionsPerUTCDay = exposureParams.maxExposureDetectionsPerDay(), minTimeBetweenDetections = exposureParams.minTimeBetweenExposureDetections(), @@ -23,7 +21,6 @@ class ExposureDetectionConfigMapper @Inject constructor() : ExposureDetectionCon } data class ExposureDetectionConfigContainer( - override val exposureDetectionConfiguration: ExposureConfiguration, override val exposureDetectionParameters: ExposureDetectionParametersAndroid, override val maxExposureDetectionsPerUTCDay: Int, override val minTimeBetweenDetections: Duration, @@ -56,54 +53,3 @@ fun ExposureDetectionParametersAndroid.minTimeBetweenExposureDetections(): Durat (24 / detectionsPerDay).let { Duration.standardHours(it.toLong()) } } } - -@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) -fun AppConfig.ApplicationConfiguration.mapRiskScoreToExposureConfiguration(): ExposureConfiguration = - ExposureConfiguration - .ExposureConfigurationBuilder() - .setTransmissionRiskScores( - this.exposureConfig.transmission.appDefined1Value, - this.exposureConfig.transmission.appDefined2Value, - this.exposureConfig.transmission.appDefined3Value, - this.exposureConfig.transmission.appDefined4Value, - this.exposureConfig.transmission.appDefined5Value, - this.exposureConfig.transmission.appDefined6Value, - this.exposureConfig.transmission.appDefined7Value, - this.exposureConfig.transmission.appDefined8Value - ) - .setDurationScores( - this.exposureConfig.duration.eq0MinValue, - this.exposureConfig.duration.gt0Le5MinValue, - this.exposureConfig.duration.gt5Le10MinValue, - this.exposureConfig.duration.gt10Le15MinValue, - this.exposureConfig.duration.gt15Le20MinValue, - this.exposureConfig.duration.gt20Le25MinValue, - this.exposureConfig.duration.gt25Le30MinValue, - this.exposureConfig.duration.gt30MinValue - ) - .setDaysSinceLastExposureScores( - this.exposureConfig.daysSinceLastExposure.ge14DaysValue, - this.exposureConfig.daysSinceLastExposure.ge12Lt14DaysValue, - this.exposureConfig.daysSinceLastExposure.ge10Lt12DaysValue, - this.exposureConfig.daysSinceLastExposure.ge8Lt10DaysValue, - this.exposureConfig.daysSinceLastExposure.ge6Lt8DaysValue, - this.exposureConfig.daysSinceLastExposure.ge4Lt6DaysValue, - this.exposureConfig.daysSinceLastExposure.ge2Lt4DaysValue, - this.exposureConfig.daysSinceLastExposure.ge0Lt2DaysValue - ) - .setAttenuationScores( - this.exposureConfig.attenuation.gt73DbmValue, - this.exposureConfig.attenuation.gt63Le73DbmValue, - this.exposureConfig.attenuation.gt51Le63DbmValue, - this.exposureConfig.attenuation.gt33Le51DbmValue, - this.exposureConfig.attenuation.gt27Le33DbmValue, - this.exposureConfig.attenuation.gt15Le27DbmValue, - this.exposureConfig.attenuation.gt10Le15DbmValue, - this.exposureConfig.attenuation.le10DbmValue - ) - .setMinimumRiskScore(this.minRiskScore) - .setDurationAtAttenuationThresholds( - this.attenuationDuration.thresholds.lower, - this.attenuationDuration.thresholds.upper - ) - .build() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/KeyDownloadParametersMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/KeyDownloadParametersMapper.kt index 4c55393ec77..af16c93e849 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/KeyDownloadParametersMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/KeyDownloadParametersMapper.kt @@ -4,8 +4,8 @@ import androidx.annotation.VisibleForTesting import dagger.Reusable import de.rki.coronawarnapp.appconfig.KeyDownloadConfig import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode -import de.rki.coronawarnapp.server.protocols.internal.AppConfig -import de.rki.coronawarnapp.server.protocols.internal.KeyDownloadParameters.KeyDownloadParametersAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.KeyDownloadParameters.KeyDownloadParametersAndroid import org.joda.time.Duration import org.joda.time.LocalDate import org.joda.time.LocalTime @@ -15,8 +15,8 @@ import javax.inject.Inject @Reusable class KeyDownloadParametersMapper @Inject constructor() : KeyDownloadConfig.Mapper { - override fun map(rawConfig: AppConfig.ApplicationConfiguration): KeyDownloadConfig { - val rawParameters = rawConfig.androidKeyDownloadParameters + override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): KeyDownloadConfig { + val rawParameters = rawConfig.keyDownloadParameters return KeyDownloadConfigContainer( individualDownloadTimeout = rawParameters.individualTimeout(), diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/RiskCalculationConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/RiskCalculationConfigMapper.kt deleted file mode 100644 index dd36d4ea99f..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/RiskCalculationConfigMapper.kt +++ /dev/null @@ -1,26 +0,0 @@ -package de.rki.coronawarnapp.appconfig.mapping - -import dagger.Reusable -import de.rki.coronawarnapp.appconfig.RiskCalculationConfig -import de.rki.coronawarnapp.server.protocols.internal.AppConfig -import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass -import de.rki.coronawarnapp.server.protocols.internal.RiskScoreClassificationOuterClass -import javax.inject.Inject - -@Reusable -class RiskCalculationConfigMapper @Inject constructor() : RiskCalculationConfig.Mapper { - - override fun map(rawConfig: AppConfig.ApplicationConfiguration): RiskCalculationConfig { - return RiskCalculationContainer( - minRiskScore = rawConfig.minRiskScore, - riskScoreClasses = rawConfig.riskScoreClasses, - attenuationDuration = rawConfig.attenuationDuration - ) - } - - data class RiskCalculationContainer( - override val minRiskScore: Int, - override val attenuationDuration: AttenuationDurationOuterClass.AttenuationDuration, - override val riskScoreClasses: RiskScoreClassificationOuterClass.RiskScoreClassification - ) : RiskCalculationConfig -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt index 870c80617d2..42b2008d5c8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt @@ -114,9 +114,7 @@ class DownloadDiagnosisKeysTask @Inject constructor( Timber.tag(TAG).d("Attempting submission to ENF") val isSubmissionSuccessful = enfClient.provideDiagnosisKeys( - keyFiles = availableKeyFiles, - configuration = exposureConfig.exposureDetectionConfiguration, - token = token + keyFiles = availableKeyFiles ) Timber.tag(TAG).d("Diagnosis Keys provided (success=%s, token=%s)", isSubmissionSuccessful, token) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index 146f92532b7..5ea6e2adf59 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -3,7 +3,6 @@ package de.rki.coronawarnapp.risk import android.text.TextUtils import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationManagerCompat -import com.google.android.gms.nearby.exposurenotification.ExposureSummary import com.google.android.gms.nearby.exposurenotification.ExposureWindow import com.google.android.gms.nearby.exposurenotification.Infectiousness import com.google.android.gms.nearby.exposurenotification.ReportType @@ -19,7 +18,6 @@ import de.rki.coronawarnapp.risk.RiskLevel.UNKNOWN_RISK_OUTDATED_RESULTS import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.risk.result.RiskResult -import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass.AttenuationDuration import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.RiskLevelRepository @@ -30,7 +28,6 @@ import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -import kotlin.math.round @Singleton class DefaultRiskLevels @Inject constructor( @@ -102,47 +99,16 @@ class DefaultRiskLevels @Inject constructor( } } - override suspend fun isIncreasedRisk(lastExposureSummary: ExposureSummary): Boolean { - val appConfiguration = appConfigProvider.getAppConfig() - Timber.tag(TAG).v("Retrieved configuration from backend") - // custom attenuation parameters to weigh the attenuation - // values provided by the Google API - val attenuationParameters = appConfiguration.attenuationDuration - // these are the defined risk classes. They will divide the calculated - // risk score into the low and increased risk - val riskScoreClassification = appConfiguration.riskScoreClasses - - // calculate the risk score based on the values collected by the Google EN API and - // the backend configuration - val riskScore = calculateRiskScore( - attenuationParameters, - lastExposureSummary - ).also { - Timber.tag(TAG).v("Calculated risk with the given config: $it") - } - - // get the high risk score class - val highRiskScoreClass = - riskScoreClassification.riskClassesList.find { it.label == "HIGH" } - ?: throw RiskLevelCalculationException(IllegalStateException("No high risk score class found")) + override fun isIncreasedRisk(exposureWindows: List): Boolean { + val riskResultsPerWindow = + exposureWindows.mapNotNull { window -> + calculateRisk(window)?.let { window to it } + }.toMap() - // if the calculated risk score is above the defined level threshold we return the high level risk score - if (withinDefinedLevelThreshold( - riskScore, - highRiskScoreClass.min, - highRiskScoreClass.max - ) - ) { - Timber.tag(TAG) - .v("$riskScore is above the defined min value ${highRiskScoreClass.min}") - return true - } else if (riskScore > highRiskScoreClass.max) { - throw RiskLevelCalculationException( - IllegalStateException("Risk score is above the max threshold for score class") - ) - } + val aggregatedResult = aggregateResults(riskResultsPerWindow) - return false + return aggregatedResult.totalRiskLevel == + RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH } override fun isActiveTracingTimeAboveThreshold(): Boolean { @@ -162,52 +128,6 @@ class DefaultRiskLevels @Inject constructor( } } - override fun calculateRiskScore( - attenuationParameters: AttenuationDuration, - exposureSummary: ExposureSummary - ): Double { - /** all attenuation values are capped to [TimeVariables.MAX_ATTENUATION_DURATION] */ - val weightedAttenuationLow = - attenuationParameters.weights.low - .times(exposureSummary.attenuationDurationsInMinutes[0].capped()) - val weightedAttenuationMid = - attenuationParameters.weights.mid - .times(exposureSummary.attenuationDurationsInMinutes[1].capped()) - val weightedAttenuationHigh = - attenuationParameters.weights.high - .times(exposureSummary.attenuationDurationsInMinutes[2].capped()) - - val maximumRiskScore = exposureSummary.maximumRiskScore.toDouble() - - val defaultBucketOffset = attenuationParameters.defaultBucketOffset.toDouble() - val normalizationDivisor = attenuationParameters.riskScoreNormalizationDivisor.toDouble() - - val attenuationStrings = - "Weighted Attenuation: ($weightedAttenuationLow + $weightedAttenuationMid + " + - "$weightedAttenuationHigh + $defaultBucketOffset)" - Timber.v(attenuationStrings) - - val weightedAttenuationDuration = - weightedAttenuationLow - .plus(weightedAttenuationMid) - .plus(weightedAttenuationHigh) - .plus(defaultBucketOffset) - - Timber.v("Formula used: ($maximumRiskScore / $normalizationDivisor) * $weightedAttenuationDuration") - - val riskScore = (maximumRiskScore / normalizationDivisor) * weightedAttenuationDuration - - return round(riskScore.times(DECIMAL_MULTIPLIER)).div(DECIMAL_MULTIPLIER) - } - - @VisibleForTesting - internal fun Int.capped() = - if (this > TimeVariables.getMaxAttenuationDuration()) { - TimeVariables.getMaxAttenuationDuration() - } else { - this - } - @VisibleForTesting internal fun withinDefinedLevelThreshold(riskScore: Double, min: Int, max: Int) = riskScore >= min && riskScore <= max diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt index a97218f1a21..fd1c6d91c03 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt @@ -1,12 +1,10 @@ package de.rki.coronawarnapp.risk import android.content.Context -import com.google.android.gms.nearby.exposurenotification.ExposureSummary +import com.google.android.gms.nearby.exposurenotification.ExposureWindow 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.nearby.InternalExposureNotificationClient 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 @@ -14,7 +12,6 @@ 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.storage.LocalData import de.rki.coronawarnapp.storage.RiskLevelRepository import de.rki.coronawarnapp.task.Task import de.rki.coronawarnapp.task.TaskCancellationException @@ -75,7 +72,7 @@ class RiskLevelTask @Inject constructor( UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL } - isIncreasedRisk(getNewExposureSummary()).also { + isIncreasedRisk(getExposureWindows()).also { checkCancel() } -> INCREASED_RISK @@ -100,23 +97,7 @@ class RiskLevelTask @Inject constructor( } } - /** - * If there is no persisted exposure summary we try to get a new one with the last persisted - * Google API token that was used in the [de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction] - * - * @return a exposure summary from the Google Exposure Notification API - */ - private suspend fun getNewExposureSummary(): ExposureSummary { - val googleToken = LocalData.googleApiToken() - ?: throw RiskLevelCalculationException(IllegalStateException("Exposure summary is not persisted")) - - val exposureSummary = - InternalExposureNotificationClient.asyncGetExposureSummary(googleToken) - - return exposureSummary.also { - Timber.tag(TAG).v("Generated new exposure summary with $googleToken") - } - } + private suspend fun getExposureWindows(): List = enfClient.exposureWindows() private fun checkCancel() { if (isCanceled) throw TaskCancellationException() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt index 28cdbf181ce..63c83ac7730 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt @@ -1,10 +1,8 @@ package de.rki.coronawarnapp.risk -import com.google.android.gms.nearby.exposurenotification.ExposureSummary import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.risk.result.RiskResult -import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass interface RiskLevels { @@ -18,19 +16,13 @@ interface RiskLevels { */ fun isActiveTracingTimeAboveThreshold(): Boolean - suspend fun isIncreasedRisk(lastExposureSummary: ExposureSummary): Boolean + fun isIncreasedRisk(exposureWindows: List): Boolean fun updateRepository( riskLevel: RiskLevel, time: Long ) - @Deprecated("Switch to new calculation with Exposure Window") - fun calculateRiskScore( - attenuationParameters: AttenuationDurationOuterClass.AttenuationDuration, - exposureSummary: ExposureSummary - ): Double - fun calculateRisk( exposureWindow: ExposureWindow ): RiskResult? diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt index 42dfb2f6023..80432862f1f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt @@ -8,7 +8,6 @@ import de.rki.coronawarnapp.BuildConfig import de.rki.coronawarnapp.R import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.download.ApplicationConfigurationCorruptException -import de.rki.coronawarnapp.server.protocols.internal.AppVersionConfig.SemanticVersion import de.rki.coronawarnapp.ui.LauncherActivity import de.rki.coronawarnapp.util.di.AppInjector import timber.log.Timber @@ -69,30 +68,19 @@ class UpdateChecker(private val activity: LauncherActivity) { private suspend fun checkIfUpdatesNeededFromServer(): Boolean { val cwaAppConfig: CWAConfig = AppInjector.component.appConfigProvider.getAppConfig() - val minVersionFromServer = cwaAppConfig.appVersion.android.min - val minVersionFromServerString = - constructSemanticVersionString(minVersionFromServer) + val minVersionFromServer = cwaAppConfig.minVersionCode Timber.e( - "minVersionStringFromServer:%s", constructSemanticVersionString( - minVersionFromServer - ) + "minVersionFromServer:%s", + minVersionFromServer ) - Timber.e("Current app version:%s", BuildConfig.VERSION_NAME) + Timber.e("Current app version:%s", BuildConfig.VERSION_CODE) val needsImmediateUpdate = VersionComparator.isVersionOlder( - BuildConfig.VERSION_NAME, - minVersionFromServerString + BuildConfig.VERSION_CODE.toLong(), + minVersionFromServer ) Timber.e("needs update:$needsImmediateUpdate") return needsImmediateUpdate } - - private fun constructSemanticVersionString( - semanticVersion: SemanticVersion - ): String { - return semanticVersion.major.toString() + "." + - semanticVersion.minor.toString() + "." + - semanticVersion.patch.toString() - } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/VersionComparator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/VersionComparator.kt index 2a706d3e1a0..117932d4f43 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/VersionComparator.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/VersionComparator.kt @@ -15,32 +15,7 @@ object VersionComparator { * @param versionToCompareTo * @return true if currentVersion is older than versionToCompareTo, else false */ - fun isVersionOlder(currentVersion: String, versionToCompareTo: String): Boolean { - var isVersionOlder = false - - val delimiter = "." - - val currentVersionParts = currentVersion.split(delimiter) - val currentVersionMajor = currentVersionParts[0].toInt() - val currentVersionMinor = currentVersionParts[1].toInt() - val currentVersionPatch = currentVersionParts[2].toInt() - - val versionToCompareParts = versionToCompareTo.split(delimiter) - val versionToCompareMajor = versionToCompareParts[0].toInt() - val versionToCompareMinor = versionToCompareParts[1].toInt() - val versionToComparePatch = versionToCompareParts[2].toInt() - - if (versionToCompareMajor > currentVersionMajor) { - isVersionOlder = true - } else if (versionToCompareMajor == currentVersionMajor) { - if (versionToCompareMinor > currentVersionMinor) { - isVersionOlder = true - } else if ((versionToCompareMinor == currentVersionMinor) && - (versionToComparePatch > currentVersionPatch) - ) { - isVersionOlder = true - } - } - return isVersionOlder + fun isVersionOlder(currentVersion: Long, versionToCompareTo: Long): Boolean { + return currentVersion < versionToCompareTo } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiTest.kt index da1ab2f7d7e..0b64b62e5b0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/AppConfigApiTest.kt @@ -49,7 +49,7 @@ class AppConfigApiTest : BaseIOTest() { testDir.deleteRecursively() } - private fun createAPI(): AppConfigApiV1 { + private fun createAPI(): AppConfigApiV2 { val httpModule = HttpModule() val defaultHttpClient = httpModule.defaultHttpClient() val gsonConverterFactory = httpModule.provideGSONConverter() @@ -76,14 +76,14 @@ class AppConfigApiTest : BaseIOTest() { webServer.enqueue(MockResponse().setBody("~appconfig")) runBlocking { - api.getApplicationConfiguration("DE").apply { + api.getApplicationConfiguration().apply { body()!!.string() shouldBe "~appconfig" } } val request = webServer.takeRequest(5, TimeUnit.SECONDS)!! request.method shouldBe "GET" - request.path shouldBe "/version/v1/configuration/country/DE/app_config" + request.path shouldBe "/version/v1/app_config_android" } @Test @@ -97,7 +97,7 @@ class AppConfigApiTest : BaseIOTest() { webServer.enqueue(configResponse) runBlocking { - api.getApplicationConfiguration("DE").apply { + api.getApplicationConfiguration().apply { body()!!.string() shouldBe "~appconfig" } } @@ -106,12 +106,12 @@ class AppConfigApiTest : BaseIOTest() { webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply { method shouldBe "GET" - path shouldBe "/version/v1/configuration/country/DE/app_config" + path shouldBe "/version/v1/app_config_android" } webServer.enqueue(configResponse) runBlocking { - api.getApplicationConfiguration("DE").apply { + api.getApplicationConfiguration().apply { body()!!.string() shouldBe "~appconfig" } } @@ -124,7 +124,7 @@ class AppConfigApiTest : BaseIOTest() { webServer.enqueue(configResponse) runBlocking { - api.getApplicationConfiguration("DE").apply { + api.getApplicationConfiguration().apply { body()!!.string() shouldBe "~appconfig" } } @@ -133,7 +133,7 @@ class AppConfigApiTest : BaseIOTest() { webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply { method shouldBe "GET" - path shouldBe "/version/v1/configuration/country/DE/app_config" + path shouldBe "/version/v1/app_config_android" } } @@ -148,7 +148,7 @@ class AppConfigApiTest : BaseIOTest() { webServer.enqueue(configResponse) runBlocking { - api.getApplicationConfiguration("DE").apply { + api.getApplicationConfiguration().apply { body()!!.string() shouldBe "~appconfig" } } @@ -157,13 +157,13 @@ class AppConfigApiTest : BaseIOTest() { webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply { method shouldBe "GET" - path shouldBe "/version/v1/configuration/country/DE/app_config" + path shouldBe "/version/v1/app_config_android" } webServer.enqueue(MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_DURING_REQUEST_BODY)) runBlocking { - api.getApplicationConfiguration("DE").apply { + api.getApplicationConfiguration().apply { body()!!.string() shouldBe "~appconfig" } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/AppConfigServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/AppConfigServerTest.kt index db28c6d6d7c..f18e927f58c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/AppConfigServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/AppConfigServerTest.kt @@ -28,7 +28,7 @@ import java.io.File class AppConfigServerTest : BaseIOTest() { - @MockK lateinit var api: AppConfigApiV1 + @MockK lateinit var api: AppConfigApiV2 @MockK lateinit var verificationKeys: VerificationKeys @MockK lateinit var timeStamper: TimeStamper private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!) @@ -54,14 +54,13 @@ class AppConfigServerTest : BaseIOTest() { private fun createInstance(homeCountry: LocationCode = defaultHomeCountry) = AppConfigServer( api = { api }, verificationKeys = verificationKeys, - homeCountry = homeCountry, cache = mockk(), timeStamper = timeStamper ) @Test fun `application config download`() = runBlockingTest { - coEvery { api.getApplicationConfiguration("DE") } returns Response.success( + coEvery { api.getApplicationConfiguration() } returns Response.success( APPCONFIG_BUNDLE.toResponseBody(), Headers.headersOf( "Date", "Tue, 03 Nov 2020 08:46:03 GMT", @@ -87,7 +86,7 @@ class AppConfigServerTest : BaseIOTest() { @Test fun `application config data is faulty`() = runBlockingTest { - coEvery { api.getApplicationConfiguration("DE") } returns Response.success( + coEvery { api.getApplicationConfiguration() } returns Response.success( "123ABC".decodeHex().toResponseBody() ) @@ -100,7 +99,7 @@ class AppConfigServerTest : BaseIOTest() { @Test fun `application config verification fails`() = runBlockingTest { - coEvery { api.getApplicationConfiguration("DE") } returns Response.success( + coEvery { api.getApplicationConfiguration() } returns Response.success( APPCONFIG_BUNDLE.toResponseBody() ) every { verificationKeys.hasInvalidSignature(any(), any()) } returns true @@ -114,7 +113,7 @@ class AppConfigServerTest : BaseIOTest() { @Test fun `missing server date leads to local time fallback`() = runBlockingTest { - coEvery { api.getApplicationConfiguration("DE") } returns Response.success( + coEvery { api.getApplicationConfiguration() } returns Response.success( APPCONFIG_BUNDLE.toResponseBody(), Headers.headersOf( "ETag", "I am an ETag :)!" @@ -134,7 +133,7 @@ class AppConfigServerTest : BaseIOTest() { @Test fun `missing server etag leads to exception`() = runBlockingTest { - coEvery { api.getApplicationConfiguration("DE") } returns Response.success( + coEvery { api.getApplicationConfiguration() } returns Response.success( APPCONFIG_BUNDLE.toResponseBody() ) @@ -147,7 +146,7 @@ class AppConfigServerTest : BaseIOTest() { @Test fun `local offset is the difference between server time and local time`() = runBlockingTest { - coEvery { api.getApplicationConfiguration("DE") } returns Response.success( + coEvery { api.getApplicationConfiguration() } returns Response.success( APPCONFIG_BUNDLE.toResponseBody(), Headers.headersOf( "Date", "Tue, 03 Nov 2020 06:35:16 GMT", @@ -183,7 +182,7 @@ class AppConfigServerTest : BaseIOTest() { every { mockCacheResponse.sentRequestAtMillis } returns Instant.parse("2020-11-03T04:35:16.000Z").millis every { response.raw().cacheResponse } returns mockCacheResponse - coEvery { api.getApplicationConfiguration("DE") } returns response + coEvery { api.getApplicationConfiguration() } returns response every { timeStamper.nowUTC } returns Instant.parse("2020-11-03T05:35:16.000Z") val downloadServer = createInstance() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/CWAConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/CWAConfigMapperTest.kt index 2ef853f468f..edebc20fd1c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/CWAConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/CWAConfigMapperTest.kt @@ -1,6 +1,6 @@ package de.rki.coronawarnapp.appconfig.mapping -import de.rki.coronawarnapp.server.protocols.internal.AppConfig +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -11,11 +11,12 @@ class CWAConfigMapperTest : BaseTest() { @Test fun `simple creation`() { - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() .addAllSupportedCountries(listOf("DE", "NL")) .build() createInstance().map(rawConfig).apply { - this.appVersion shouldBe rawConfig.appVersion + this.latestVersionCode shouldBe rawConfig.latestVersionCode + this.minVersionCode shouldBe rawConfig.minVersionCode this.supportedCountries shouldBe listOf("DE", "NL") } } @@ -23,11 +24,12 @@ class CWAConfigMapperTest : BaseTest() { @Test fun `invalid supported countries are filtered out`() { // Could happen due to protobuf scheme missmatch - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() .addAllSupportedCountries(listOf("plausible deniability")) .build() createInstance().map(rawConfig).apply { - this.appVersion shouldBe rawConfig.appVersion + this.latestVersionCode shouldBe rawConfig.latestVersionCode + this.minVersionCode shouldBe rawConfig.minVersionCode this.supportedCountries shouldBe emptyList() } } @@ -35,10 +37,11 @@ class CWAConfigMapperTest : BaseTest() { @Test fun `if supportedCountryList is empty, we do not insert DE as fallback`() { // Because the UI requires this to detect when to show alternative UI elements - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() .build() createInstance().map(rawConfig).apply { - this.appVersion shouldBe rawConfig.appVersion + this.latestVersionCode shouldBe rawConfig.latestVersionCode + this.minVersionCode shouldBe rawConfig.minVersionCode this.supportedCountries shouldBe emptyList() } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt index 55e68f895f3..22f65553bf1 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt @@ -4,7 +4,6 @@ import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.KeyDownloadConfig -import de.rki.coronawarnapp.appconfig.RiskCalculationConfig import io.mockk.MockKAnnotations import io.mockk.clearAllMocks import io.mockk.every @@ -21,7 +20,6 @@ class ConfigParserTest : BaseTest() { @MockK lateinit var cwaConfigMapper: CWAConfig.Mapper @MockK lateinit var keyDownloadConfigMapper: KeyDownloadConfig.Mapper @MockK lateinit var exposureDetectionConfigMapper: ExposureDetectionConfig.Mapper - @MockK lateinit var riskCalculationConfigMapper: RiskCalculationConfig.Mapper @MockK lateinit var exposureWindowRiskCalculationConfigMapper: ExposureWindowRiskCalculationConfig.Mapper @BeforeEach @@ -31,7 +29,6 @@ class ConfigParserTest : BaseTest() { every { cwaConfigMapper.map(any()) } returns mockk() every { keyDownloadConfigMapper.map(any()) } returns mockk() every { exposureDetectionConfigMapper.map(any()) } returns mockk() - every { riskCalculationConfigMapper.map(any()) } returns mockk() every { exposureWindowRiskCalculationConfigMapper.map(any()) } returns mockk() } @@ -44,7 +41,6 @@ class ConfigParserTest : BaseTest() { cwaConfigMapper = cwaConfigMapper, keyDownloadConfigMapper = keyDownloadConfigMapper, exposureDetectionConfigMapper = exposureDetectionConfigMapper, - riskCalculationConfigMapper = riskCalculationConfigMapper, exposureWindowRiskCalculationConfigMapper = exposureWindowRiskCalculationConfigMapper ) @@ -56,7 +52,6 @@ class ConfigParserTest : BaseTest() { cwaConfigMapper.map(any()) keyDownloadConfigMapper.map(any()) exposureDetectionConfigMapper.map(any()) - riskCalculationConfigMapper.map(any()) exposureWindowRiskCalculationConfigMapper.map(any()) } } @@ -64,12 +59,21 @@ class ConfigParserTest : BaseTest() { companion object { private val APPCONFIG_RAW = ( - "080b124d0a230a034c4f57180f221a68747470733a2f2f777777" + - "2e636f726f6e617761726e2e6170700a260a0448494748100f1848221a68747470733a2f2f7777772e636f7" + - "26f6e617761726e2e6170701a640a10080110021803200428053006380740081100000000000049401a0a20" + - "0128013001380140012100000000000049402a1008051005180520052805300538054005310000000000003" + - "4403a0e1001180120012801300138014001410000000000004940221c0a040837103f121209000000000000" + - "f03f11000000000000e03f20192a1a0a0a0a041008180212021005120c0a0408011804120408011804" + "081f101f1a0e0a0c0a0872657365727665641001220244452a061" + + "8c20320e003320508061084073ad4010a0d0a0b1900000000004" + + "0524020010a0d120b190000000000002440200112140a1209000" + + "000000000f03f1900000000000000401a160a0b1900000000008" + + "04b40200111000000000000f03f1a1f0a14090000000000804b4" + + "0190000000000804f40200111000000000000e03f220f0a0b190" + + "000000000002e402001100122160a12090000000000002e40190" + + "00000008087c34010022a0f0a0b190000000000002e402001100" + + "12a160a12090000000000002e4019000000008087c3401002320" + + "a10041804200328023001399a9999999999c93f420c0a0408011" + + "0010a04080210024a750a031e32461220000000000000f03f000" + + "000000000f03f000000000000f03f000000000000f03f220b080" + + "111000000000000f03f220b080211000000000000f03f320b080" + + "111000000000000f03f320b080211000000000000f03f320b080" + + "311000000000000f03f320b080411000000000000f03f" ).decodeHex() } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt index 2d0ab66e16a..e9e865ba3d0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt @@ -1,8 +1,8 @@ package de.rki.coronawarnapp.appconfig.mapping import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode -import de.rki.coronawarnapp.server.protocols.internal.AppConfig -import de.rki.coronawarnapp.server.protocols.internal.KeyDownloadParameters +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.KeyDownloadParameters import io.kotest.matchers.shouldBe import org.joda.time.LocalDate import org.joda.time.LocalTime @@ -22,8 +22,8 @@ class DownloadConfigMapperTest : BaseTest() { }.let { addRevokedDayPackages(it) } } - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() - .setAndroidKeyDownloadParameters(builder) + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() + .setKeyDownloadParameters(builder) .build() createInstance().map(rawConfig).apply { @@ -46,8 +46,8 @@ class DownloadConfigMapperTest : BaseTest() { }.let { addRevokedHourPackages(it) } } - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() - .setAndroidKeyDownloadParameters(builder) + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() + .setKeyDownloadParameters(builder) .build() createInstance().map(rawConfig).apply { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt index 7812c23aba1..7b2044a8baf 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.appconfig.mapping -import de.rki.coronawarnapp.server.protocols.internal.AppConfig -import de.rki.coronawarnapp.server.protocols.internal.ExposureDetectionParameters.ExposureDetectionParametersAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.ExposureDetectionParameters.ExposureDetectionParametersAndroid import io.kotest.matchers.shouldBe import org.joda.time.Duration import org.junit.jupiter.api.Test @@ -13,21 +13,18 @@ class ExposureDetectionConfigMapperTest : BaseTest() { @Test fun `simple creation`() { - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() - .setMinRiskScore(1) + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() .build() createInstance().map(rawConfig).apply { - exposureDetectionConfiguration shouldBe rawConfig.mapRiskScoreToExposureConfiguration() - exposureDetectionParameters shouldBe rawConfig.androidExposureDetectionParameters + exposureDetectionParameters shouldBe rawConfig.exposureDetectionParameters } } @Test fun `detection interval 0 defaults to almost infinite delay`() { val exposureDetectionParameters = ExposureDetectionParametersAndroid.newBuilder() - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() - .setMinRiskScore(1) - .setAndroidExposureDetectionParameters(exposureDetectionParameters) + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() + .setExposureDetectionParameters(exposureDetectionParameters) .build() createInstance().map(rawConfig).apply { minTimeBetweenDetections shouldBe Duration.standardDays(99) @@ -40,9 +37,8 @@ class ExposureDetectionConfigMapperTest : BaseTest() { val exposureDetectionParameters = ExposureDetectionParametersAndroid.newBuilder().apply { maxExposureDetectionsPerInterval = 3 } - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() - .setMinRiskScore(1) - .setAndroidExposureDetectionParameters(exposureDetectionParameters) + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() + .setExposureDetectionParameters(exposureDetectionParameters) .build() createInstance().map(rawConfig).apply { minTimeBetweenDetections shouldBe Duration.standardHours(24 / 3) @@ -55,9 +51,8 @@ class ExposureDetectionConfigMapperTest : BaseTest() { val exposureDetectionParameters = ExposureDetectionParametersAndroid.newBuilder().apply { overallTimeoutInSeconds = 10 * 60 } - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() - .setMinRiskScore(1) - .setAndroidExposureDetectionParameters(exposureDetectionParameters) + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() + .setExposureDetectionParameters(exposureDetectionParameters) .build() createInstance().map(rawConfig).apply { overallDetectionTimeout shouldBe Duration.standardMinutes(10) @@ -69,9 +64,8 @@ class ExposureDetectionConfigMapperTest : BaseTest() { val exposureDetectionParameters = ExposureDetectionParametersAndroid.newBuilder().apply { overallTimeoutInSeconds = 0 } - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() - .setMinRiskScore(1) - .setAndroidExposureDetectionParameters(exposureDetectionParameters) + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() + .setExposureDetectionParameters(exposureDetectionParameters) .build() createInstance().map(rawConfig).apply { overallDetectionTimeout shouldBe Duration.standardMinutes(15) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/RiskCalculationConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/RiskCalculationConfigMapperTest.kt deleted file mode 100644 index e0cf0e3c09a..00000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/RiskCalculationConfigMapperTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package de.rki.coronawarnapp.appconfig.mapping - -import de.rki.coronawarnapp.server.protocols.internal.AppConfig -import io.kotest.matchers.shouldBe -import org.junit.jupiter.api.Test -import testhelpers.BaseTest - -class RiskCalculationConfigMapperTest : BaseTest() { - - private fun createInstance() = RiskCalculationConfigMapper() - - @Test - fun `simple creation`() { - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() - .build() - createInstance().map(rawConfig).apply { - this.attenuationDuration shouldBe rawConfig.attenuationDuration - this.minRiskScore shouldBe rawConfig.minRiskScore - this.riskScoreClasses shouldBe rawConfig.riskScoreClasses - } - } -} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt index 746c8acf70b..3bc127ce14b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt @@ -1,15 +1,12 @@ package de.rki.coronawarnapp.risk -import com.google.android.gms.nearby.exposurenotification.ExposureSummary import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk -import junit.framework.TestCase.assertEquals import org.junit.Before import org.junit.Test import testhelpers.BaseTest @@ -44,108 +41,4 @@ class RiskLevelsTest : BaseTest() { riskLevels.withinDefinedLevelThreshold(1.0, 1, 3) shouldBe true riskLevels.withinDefinedLevelThreshold(3.0, 1, 3) shouldBe true } - - @Test - fun calculateRiskScoreZero() { - val riskScore = - riskLevels.calculateRiskScore( - buildAttenuationDuration(0.5, 0.5, 1.0), - buildSummary(0, 0, 0, 0) - ) - - assertEquals(0.0, riskScore) - } - - @Test - fun calculateRiskScoreLow() { - val riskScore = - riskLevels.calculateRiskScore( - buildAttenuationDuration(0.5, 0.5, 1.0), - buildSummary(156, 10, 10, 10) - ) - - assertEquals(124.8, riskScore) - } - - @Test - fun calculateRiskScoreMid() { - val riskScore = - riskLevels.calculateRiskScore( - buildAttenuationDuration(0.5, 0.5, 1.0), - buildSummary(256, 15, 15, 15) - ) - - assertEquals(307.2, riskScore) - } - - @Test - fun calculateRiskScoreHigh() { - val riskScore = - riskLevels.calculateRiskScore( - buildAttenuationDuration(0.5, 0.5, 1.0), - buildSummary(512, 30, 30, 30) - ) - - assertEquals(1228.8, riskScore) - } - - @Test - fun calculateRiskScoreMax() { - val riskScore = - riskLevels.calculateRiskScore( - buildAttenuationDuration(0.5, 0.5, 1.0), - buildSummary(4096, 30, 30, 30) - ) - - assertEquals(9830.4, riskScore) - } - - @Test - fun calculateRiskScoreCapped() { - val riskScore = - riskLevels.calculateRiskScore( - buildAttenuationDuration(0.5, 0.5, 1.0), - buildSummary(4096, 45, 45, 45) - ) - - assertEquals(9830.4, riskScore) - } - - private fun buildAttenuationDuration( - high: Double, - mid: Double, - low: Double, - norm: Int = 25, - offset: Int = 0 - ): AttenuationDurationOuterClass.AttenuationDuration { - return AttenuationDurationOuterClass.AttenuationDuration - .newBuilder() - .setRiskScoreNormalizationDivisor(norm) - .setDefaultBucketOffset(offset) - .setWeights( - AttenuationDurationOuterClass.Weights - .newBuilder() - .setHigh(high) - .setMid(mid) - .setLow(low) - .build() - ) - .build() - } - - private fun buildSummary( - maxRisk: Int = 0, - lowAttenuation: Int = 0, - midAttenuation: Int = 0, - highAttenuation: Int = 0 - ): ExposureSummary { - val intArray = IntArray(3) - intArray[0] = lowAttenuation - intArray[1] = midAttenuation - intArray[2] = highAttenuation - return ExposureSummary.ExposureSummaryBuilder() - .setMaximumRiskScore(maxRisk) - .setAttenuationDurations(intArray) - .build() - } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/VersionComparatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/VersionComparatorTest.kt index 5b92e22d288..e904e2159df 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/VersionComparatorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/VersionComparatorTest.kt @@ -8,55 +8,55 @@ class VersionComparatorTest { @Test fun testVersionMajorOlder() { - val result = VersionComparator.isVersionOlder("1.0.0", "2.0.0") + val result = VersionComparator.isVersionOlder(1000000, 2000000) assertThat(result, `is`(true)) } @Test fun testVersionMinorOlder() { - val result = VersionComparator.isVersionOlder("1.0.0", "1.1.0") + val result = VersionComparator.isVersionOlder(1000000, 1010000) assertThat(result, `is`(true)) } @Test fun testVersionPatchOlder() { - val result = VersionComparator.isVersionOlder("1.0.1", "1.0.2") + val result = VersionComparator.isVersionOlder(1000100, 1000200) assertThat(result, `is`(true)) } @Test fun testVersionMajorNewer() { - val result = VersionComparator.isVersionOlder("2.0.0", "1.0.0") + val result = VersionComparator.isVersionOlder(2000000, 1000000) assertThat(result, `is`(false)) } @Test fun testVersionMinorNewer() { - val result = VersionComparator.isVersionOlder("1.2.0", "1.1.0") + val result = VersionComparator.isVersionOlder(1020000, 1010000) assertThat(result, `is`(false)) } @Test fun testVersionPatchNewer() { - val result = VersionComparator.isVersionOlder("1.0.3", "1.0.2") + val result = VersionComparator.isVersionOlder(1000300, 1000200) assertThat(result, `is`(false)) } @Test fun testSameVersion() { - val result = VersionComparator.isVersionOlder("1.0.1", "1.0.1") + val result = VersionComparator.isVersionOlder(1000100, 1000100) assertThat(result, `is`(false)) } @Test fun testIfMajorIsNewerButMinorSmallerNumber() { - val result = VersionComparator.isVersionOlder("3.1.0", "1.2.0") + val result = VersionComparator.isVersionOlder(3010000, 1020000) assertThat(result, `is`(false)) } @Test fun testIfMinorIsNewerButPatchSmallerNumber() { - val result = VersionComparator.isVersionOlder("1.3.1", "1.2.4") + val result = VersionComparator.isVersionOlder(1030100, 1020400) assertThat(result, `is`(false)) } } diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt index 0413a8515b6..2eb25af0cdc 100644 --- a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt +++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.test.risklevel.ui import android.content.Context import androidx.lifecycle.SavedStateHandle import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.risk.RiskLevels @@ -39,6 +40,7 @@ class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() { @MockK lateinit var tracingCardStateProvider: TracingCardStateProvider @MockK lateinit var taskController: TaskController @MockK lateinit var riskLevels: RiskLevels + @MockK lateinit var appConfigProvider: AppConfigProvider @BeforeEach fun setup() { @@ -65,7 +67,8 @@ class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() { tracingCardStateProvider = tracingCardStateProvider, dispatcherProvider = TestDispatcherProvider, riskLevels = riskLevels, - taskController = taskController + taskController = taskController, + appConfigProvider = appConfigProvider ) @Test From fb77079690c857065624f2314d676e7131171824 Mon Sep 17 00:00:00 2001 From: BMItter <46747780+BMItter@users.noreply.github.com> Date: Tue, 17 Nov 2020 13:55:18 +0100 Subject: [PATCH 07/47] ENFv2 -Cleanup deprecations and corresponding tests (EXPOSUREAPP-3456) (#1634) * Adjusted tests accordingly - removed deprecated provideDiagnosisKeys * satisfy detekt --- .../de/rki/coronawarnapp/nearby/ENFClient.kt | 10 -- .../DefaultDiagnosisKeyProvider.kt | 106 ++------------ .../DiagnosisKeyProvider.kt | 19 +-- .../rki/coronawarnapp/nearby/ENFClientTest.kt | 9 +- .../DefaultDiagnosisKeyProviderTest.kt | 136 ++++-------------- 5 files changed, 43 insertions(+), 237 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt index 9e2b23d155e..b2a7d5f6a39 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.nearby -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker @@ -35,15 +34,6 @@ class ENFClient @Inject constructor( internal val internalClient: ExposureNotificationClient get() = googleENFClient - override suspend fun provideDiagnosisKeys( - keyFiles: Collection, - configuration: ExposureConfiguration?, - token: String - ): Boolean { - // TODO uncomment Exception later, after every subtask has joined (fun will probably be removed) - throw UnsupportedOperationException("Use provideDiagnosisKeys without token and configuration!") - } - override suspend fun provideDiagnosisKeys(keyFiles: Collection): Boolean { Timber.d("asyncProvideDiagnosisKeys(keyFiles=$keyFiles)") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt index a0cbf8b53d1..ebf8523d7de 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt @@ -1,9 +1,6 @@ -@file:Suppress("DEPRECATION") - package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider import com.google.android.gms.common.api.ApiException -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import de.rki.coronawarnapp.exception.reporting.ReportingConstants import de.rki.coronawarnapp.util.GoogleAPIVersion @@ -22,38 +19,6 @@ class DefaultDiagnosisKeyProvider @Inject constructor( private val enfClient: ExposureNotificationClient ) : DiagnosisKeyProvider { - override suspend fun provideDiagnosisKeys( - keyFiles: Collection, - configuration: ExposureConfiguration?, - token: String - ): Boolean { - return try { - if (keyFiles.isEmpty()) { - Timber.d("No key files submitted, returning early.") - return true - } - - val usedConfiguration = if (configuration == null) { - Timber.w("Passed configuration was NULL, creating fallback.") - ExposureConfiguration.ExposureConfigurationBuilder().build() - } else { - configuration - } - - if (googleAPIVersion.isAtLeast(GoogleAPIVersion.V16)) { - provideKeys(keyFiles, usedConfiguration, token) - } else { - provideKeysLegacy(keyFiles, usedConfiguration, token) - } - } catch (e: Exception) { - Timber.e( - e, "Error during provideDiagnosisKeys(keyFiles=%s, configuration=%s, token=%s)", - keyFiles, configuration, token - ) - throw e - } - } - override suspend fun provideDiagnosisKeys(keyFiles: Collection): Boolean { if (keyFiles.isEmpty()) { Timber.d("No key files submitted, returning early.") @@ -68,6 +33,7 @@ class DefaultDiagnosisKeyProvider @Inject constructor( if (!submissionQuota.consumeQuota(1)) { Timber.w("No key files submitted because not enough quota available.") + return false } return suspendCoroutine { cont -> @@ -75,67 +41,15 @@ class DefaultDiagnosisKeyProvider @Inject constructor( enfClient .provideDiagnosisKeys(keyFiles.toList()) .addOnSuccessListener { cont.resume(true) } - .addOnFailureListener { cont.resumeWithException(it) } - } - } - - private suspend fun provideKeys( - files: Collection, - configuration: ExposureConfiguration, - token: String - ): Boolean { - Timber.d("Using non-legacy key provision.") - - if (!submissionQuota.consumeQuota(1)) { - Timber.w("Not enough quota available.") - // TODO Currently only logging, we'll be more strict in a future release - // return false - } - - performSubmission(files, configuration, token) - return true - } - - /** - * We use Batch Size 1 and thus submit multiple times to the API. - * This means that instead of directly submitting all files at once, we have to split up - * our file list as this equals a different batch for Google every time. - */ - private suspend fun provideKeysLegacy( - keyFiles: Collection, - configuration: ExposureConfiguration, - token: String - ): Boolean { - Timber.d("Using LEGACY key provision.") - - if (!submissionQuota.consumeQuota(keyFiles.size)) { - Timber.w("Not enough quota available.") - // TODO What about proceeding with partial submission? - // TODO Currently only logging, we'll be more strict in a future release - // return false - } - - keyFiles.forEach { performSubmission(listOf(it), configuration, token) } - return true - } - - private suspend fun performSubmission( - keyFiles: Collection, - configuration: ExposureConfiguration, - token: String - ): Void = suspendCoroutine { cont -> - Timber.d("Performing key submission.") - enfClient - .provideDiagnosisKeys(keyFiles.toList(), configuration, token) - .addOnSuccessListener { cont.resume(it) } - .addOnFailureListener { - val wrappedException = when { - it is ApiException && it.statusCode == ReportingConstants.STATUS_CODE_REACHED_REQUEST_LIMIT -> { - QuotaExceededException(cause = it) - } - else -> it + .addOnFailureListener { + val wrappedException = + when (it is ApiException && + it.statusCode == ReportingConstants.STATUS_CODE_REACHED_REQUEST_LIMIT) { + true -> QuotaExceededException(cause = it) + false -> it + } + cont.resumeWithException(wrappedException) } - cont.resumeWithException(wrappedException) - } + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt index d4a2f00fa1a..b3339619f5b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt @@ -1,28 +1,19 @@ package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import java.io.File interface DiagnosisKeyProvider { /** - * Takes an ExposureConfiguration object. Inserts a list of files that contain key - * information into the on-device database. Provide the keys of confirmed cases retrieved - * from your internet-accessible server to the Google Play service once requested from the - * API. Information about the file format is in the Exposure Key Export File Format and - * Verification document that is linked from google.com/covid19/exposurenotifications. + * Inserts a list of files that contain key information into the on-device database. + * Provide the keys of confirmed cases retrieved from your internet-accessible server to + * the Google Play service once requested from the API. Information about the file format + * is in the Exposure Key Export File Format and Verification document that is linked + * from google.com/covid19/exposurenotifications. * * @param keyFiles - * @param configuration - * @param token * @return */ - @Deprecated("Use provideDiagnosisKeys with only keyFiles as param to activate WindowExposure mode") - suspend fun provideDiagnosisKeys( - keyFiles: Collection, - configuration: ExposureConfiguration?, - token: String - ): Boolean suspend fun provideDiagnosisKeys(keyFiles: Collection): Boolean } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt index 35a4efbfe52..624df3ea1a0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt @@ -1,6 +1,5 @@ package de.rki.coronawarnapp.nearby -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker @@ -18,7 +17,6 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just -import io.mockk.mockk import io.mockk.verifySequence import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf @@ -31,7 +29,6 @@ import org.junit.jupiter.api.Test import testhelpers.BaseTest import java.io.File -@Suppress("DEPRECATION") class ENFClientTest : BaseTest() { @MockK lateinit var googleENFClient: ExposureNotificationClient @@ -45,7 +42,7 @@ class ENFClientTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns true + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true every { exposureDetectionTracker.trackNewExposureDetection(any()) } just Runs } @@ -74,8 +71,6 @@ class ENFClientTest : BaseTest() { fun `provide diagnosis key call is forwarded to the right module`() { val client = createClient() val keyFiles = listOf(File("test")) - val configuration = mockk() - val token = "123" coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true runBlocking { @@ -98,8 +93,6 @@ class ENFClientTest : BaseTest() { fun `provide diagnosis key call is only forwarded if there are actually key files`() { val client = createClient() val keyFiles = emptyList() - val configuration = mockk() - val token = "123" coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true runBlocking { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt index 24fbd423aca..6ef70a9cddd 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt @@ -1,10 +1,8 @@ -@file:Suppress("DEPRECATION") - package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import de.rki.coronawarnapp.util.GoogleAPIVersion +import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.clearAllMocks import io.mockk.coEvery @@ -28,10 +26,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { @MockK lateinit var submissionQuota: SubmissionQuota - @MockK - lateinit var exampleConfiguration: ExposureConfiguration private val exampleKeyFiles = listOf(File("file1"), File("file2")) - private val exampleToken = "123e4567-e89b-12d3-a456-426655440000" @BeforeEach fun setup() { @@ -39,15 +34,9 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { coEvery { submissionQuota.consumeQuota(any()) } returns true - coEvery { - googleENFClient.provideDiagnosisKeys( - any(), - any(), - any() - ) - } returns MockGMSTask.forValue(null) + coEvery { googleENFClient.provideDiagnosisKeys(any>()) } returns MockGMSTask.forValue(null) - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns true + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true } @AfterEach @@ -62,135 +51,64 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { ) @Test - fun `legacy key provision is used on older ENF versions`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns false + fun `key provision is used on older ENF versions`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns false val provider = createProvider() - runBlocking { - provider.provideDiagnosisKeys(exampleKeyFiles, exampleConfiguration, exampleToken) - } + runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe false coVerify(exactly = 0) { - googleENFClient.provideDiagnosisKeys( - exampleKeyFiles, exampleConfiguration, exampleToken - ) - } - - coVerify(exactly = 1) { - googleENFClient.provideDiagnosisKeys( - listOf(exampleKeyFiles[0]), exampleConfiguration, exampleToken - ) - googleENFClient.provideDiagnosisKeys( - listOf(exampleKeyFiles[1]), exampleConfiguration, exampleToken - ) + googleENFClient.provideDiagnosisKeys(exampleKeyFiles) + googleENFClient.provideDiagnosisKeys(listOf(exampleKeyFiles[0])) + googleENFClient.provideDiagnosisKeys(listOf(exampleKeyFiles[1])) submissionQuota.consumeQuota(2) } } @Test - fun `normal key provision is used on newer ENF versions`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns true + fun `key provision is used on newer ENF versions`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true val provider = createProvider() - runBlocking { - provider.provideDiagnosisKeys(exampleKeyFiles, exampleConfiguration, exampleToken) - } + runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe true coVerify(exactly = 1) { - googleENFClient.provideDiagnosisKeys(any(), any(), any()) - googleENFClient.provideDiagnosisKeys( - exampleKeyFiles, exampleConfiguration, exampleToken - ) + googleENFClient.provideDiagnosisKeys(any>()) + googleENFClient.provideDiagnosisKeys(exampleKeyFiles) submissionQuota.consumeQuota(1) } } @Test - fun `passing an a null configuration leads to constructing a fallback from defaults`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns true - - val provider = createProvider() - val fallback = ExposureConfiguration.ExposureConfigurationBuilder().build() - - runBlocking { - provider.provideDiagnosisKeys(exampleKeyFiles, null, exampleToken) - } - - coVerify(exactly = 1) { - googleENFClient.provideDiagnosisKeys(any(), any(), any()) - googleENFClient.provideDiagnosisKeys(exampleKeyFiles, fallback, exampleToken) - } - } - - @Test - fun `passing an a null configuration leads to constructing a fallback from defaults, legacy`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns false - - val provider = createProvider() - val fallback = ExposureConfiguration.ExposureConfigurationBuilder().build() - - runBlocking { - provider.provideDiagnosisKeys(exampleKeyFiles, null, exampleToken) - } - - coVerify(exactly = 1) { - googleENFClient.provideDiagnosisKeys( - listOf(exampleKeyFiles[0]), fallback, exampleToken - ) - googleENFClient.provideDiagnosisKeys( - listOf(exampleKeyFiles[1]), fallback, exampleToken - ) - submissionQuota.consumeQuota(2) - } - } - - @Test - fun `quota is consumed silenently`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns true + fun `quota is consumed silently`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true coEvery { submissionQuota.consumeQuota(any()) } returns false val provider = createProvider() - runBlocking { - provider.provideDiagnosisKeys(exampleKeyFiles, exampleConfiguration, exampleToken) - } + runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe false - coVerify(exactly = 1) { - googleENFClient.provideDiagnosisKeys(any(), any(), any()) - googleENFClient.provideDiagnosisKeys( - exampleKeyFiles, exampleConfiguration, exampleToken - ) - submissionQuota.consumeQuota(1) + coVerify(exactly = 0) { + googleENFClient.provideDiagnosisKeys(any>()) + googleENFClient.provideDiagnosisKeys(exampleKeyFiles) } + + coVerify(exactly = 1) { submissionQuota.consumeQuota(1) } } @Test - fun `quota is consumed silently, legacy`() { - coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V16) } returns false - coEvery { submissionQuota.consumeQuota(any()) } returns false + fun `provide empty key list`() { + coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true val provider = createProvider() - runBlocking { - provider.provideDiagnosisKeys(exampleKeyFiles, exampleConfiguration, exampleToken) - } + runBlocking { provider.provideDiagnosisKeys(emptyList()) } shouldBe true coVerify(exactly = 0) { - googleENFClient.provideDiagnosisKeys( - exampleKeyFiles, exampleConfiguration, exampleToken - ) - } - - coVerify(exactly = 1) { - googleENFClient.provideDiagnosisKeys( - listOf(exampleKeyFiles[0]), exampleConfiguration, exampleToken - ) - googleENFClient.provideDiagnosisKeys( - listOf(exampleKeyFiles[1]), exampleConfiguration, exampleToken - ) - submissionQuota.consumeQuota(2) + googleENFClient.provideDiagnosisKeys(any>()) + googleENFClient.provideDiagnosisKeys(emptyList()) } } } From e46a5d8e6d846b7b83a73f25d4caf1735c4a20c4 Mon Sep 17 00:00:00 2001 From: BMItter <46747780+BMItter@users.noreply.github.com> Date: Wed, 18 Nov 2020 13:22:13 +0100 Subject: [PATCH 08/47] ENFv2 - Adjustments for new tech spec (EXPOSUREAPP-3853) (#1647) * Added numberOfDaysWithHighRisk and numberOfDaysWithLowRisk according to new tech spec * Transformed minimumDistinctEncountersForRisk and mostRecentDateForRisk into extension fun * typealias for ProtobufRiskLevels Signed-off-by: Kolya Opahle Co-authored-by: Kolya Opahle --- .../coronawarnapp/risk/DefaultRiskLevels.kt | 67 ++++++++++--------- .../risk/result/AggregatedRiskResult.kt | 4 +- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index 4dfcc3bec4b..07481acc9ce 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -28,6 +28,7 @@ import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton +typealias ProtoRiskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel @Singleton class DefaultRiskLevels @Inject constructor( @@ -106,8 +107,7 @@ class DefaultRiskLevels @Inject constructor( val aggregatedResult = aggregateResults(riskResultsPerWindow) - return aggregatedResult.totalRiskLevel == - RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH + return aggregatedResult.totalRiskLevel == ProtoRiskLevel.HIGH } override fun isActiveTracingTimeAboveThreshold(): Boolean { @@ -345,18 +345,14 @@ class DefaultRiskLevels @Inject constructor( Timber.d("totalRiskLevel: ${totalRiskLevel.name} (${totalRiskLevel.ordinal})") // 7. Determine `Date of Most Recent Date with Low Risk` - val mostRecentDateWithLowRisk = mostRecentDateForRisk( - exposureHistory, - RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW - ) + val mostRecentDateWithLowRisk = + exposureHistory.mostRecentDateForRisk(ProtoRiskLevel.LOW) Timber.d("mostRecentDateWithLowRisk: $mostRecentDateWithLowRisk") // 8. Determine `Date of Most Recent Date with High Risk` - val mostRecentDateWithHighRisk = mostRecentDateForRisk( - exposureHistory, - RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH - ) + val mostRecentDateWithHighRisk = + exposureHistory.mostRecentDateForRisk(ProtoRiskLevel.HIGH) Timber.d("mostRecentDateWithHighRisk: $mostRecentDateWithHighRisk") @@ -372,22 +368,37 @@ class DefaultRiskLevels @Inject constructor( Timber.d("totalMinimumDistinctEncountersWithHighRisk: $totalMinimumDistinctEncountersWithHighRisk") + // 11. Determine `Number of Days With Low Risk` + val numberOfDaysWithLowRisk = + exposureHistory.numberOfDaysForRisk(ProtoRiskLevel.LOW) + + Timber.d("numberOfDaysWithLowRisk: $numberOfDaysWithLowRisk") + + // 12. Determine `Number of Days With High Risk` + val numberOfDaysWithHighRisk = + exposureHistory.numberOfDaysForRisk(ProtoRiskLevel.HIGH) + + Timber.d("numberOfDaysWithHighRisk: $numberOfDaysWithHighRisk") + return AggregatedRiskResult( totalRiskLevel, totalMinimumDistinctEncountersWithLowRisk, totalMinimumDistinctEncountersWithHighRisk, mostRecentDateWithLowRisk, - mostRecentDateWithHighRisk + mostRecentDateWithHighRisk, + numberOfDaysWithLowRisk, + numberOfDaysWithHighRisk ) } - private fun mostRecentDateForRisk( - exposureHistory: List, - riskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel - ): Instant? = exposureHistory - .filter { it.riskLevel == riskLevel } - .maxOfOrNull { it.dateMillisSinceEpoch } - ?.let { Instant.ofEpochMilli(it) } + private fun List.mostRecentDateForRisk(riskLevel: ProtoRiskLevel): Instant? = + filter { it.riskLevel == riskLevel } + .maxOfOrNull { it.dateMillisSinceEpoch } + ?.let { Instant.ofEpochMilli(it) } + + private fun List.numberOfDaysForRisk(riskLevel: ProtoRiskLevel): Int = + filter { it.riskLevel == riskLevel } + .size private fun aggregateRiskPerDate( dateMillisSinceEpoch: Long, @@ -419,18 +430,14 @@ class DefaultRiskLevels @Inject constructor( Timber.d("riskLevel: ${riskLevel.name} (${riskLevel.ordinal})") // 4. Determine `Minimum Distinct Encounters With Low Risk per Date` - val minimumDistinctEncountersWithLowRisk = minimumDistinctEncountersForRisk( - exposureWindowsAndResultForDate, - RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW - ) + val minimumDistinctEncountersWithLowRisk = + exposureWindowsAndResultForDate.minimumDistinctEncountersForRisk(ProtoRiskLevel.LOW) Timber.d("minimumDistinctEncountersWithLowRisk: $minimumDistinctEncountersWithLowRisk") // 5. Determine `Minimum Distinct Encounters With High Risk per Date` - val minimumDistinctEncountersWithHighRisk = minimumDistinctEncountersForRisk( - exposureWindowsAndResultForDate, - RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH - ) + val minimumDistinctEncountersWithHighRisk = + exposureWindowsAndResultForDate.minimumDistinctEncountersForRisk(ProtoRiskLevel.HIGH) Timber.d("minimumDistinctEncountersWithHighRisk: $minimumDistinctEncountersWithHighRisk") @@ -442,12 +449,8 @@ class DefaultRiskLevels @Inject constructor( ) } - private fun minimumDistinctEncountersForRisk( - exposureWindowsAndResultForDate: Map, - riskLevel: RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel - ): Int = - exposureWindowsAndResultForDate - .filter { it.value.riskLevel == riskLevel } + private fun Map.minimumDistinctEncountersForRisk(riskLevel: ProtoRiskLevel): Int = + filter { it.value.riskLevel == riskLevel } .map { "${it.value.transmissionRiskLevel}_${it.key.calibrationConfidence}" } .distinct() .size diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt index 17e48ce7644..d657eb24ef9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/AggregatedRiskResult.kt @@ -8,5 +8,7 @@ data class AggregatedRiskResult( val totalMinimumDistinctEncountersWithLowRisk: Int, val totalMinimumDistinctEncountersWithHighRisk: Int, val mostRecentDateWithLowRisk: Instant?, - val mostRecentDateWithHighRisk: Instant? + val mostRecentDateWithHighRisk: Instant?, + val numberOfDaysWithLowRisk: Int, + val numberOfDaysWithHighRisk: Int ) From 94ca573229398642b405106fe5f97b10ed2c97dd Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Wed, 18 Nov 2020 15:23:17 +0100 Subject: [PATCH 09/47] Fixed asset appconfig resolution for v2 config --- .../coronawarnapp/appconfig/download/DefaultAppConfigSource.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/DefaultAppConfigSource.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/DefaultAppConfigSource.kt index ddb4e4528de..d4e33eeb5d1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/DefaultAppConfigSource.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/download/DefaultAppConfigSource.kt @@ -11,6 +11,6 @@ class DefaultAppConfigSource @Inject constructor( ) { fun getRawDefaultConfig(): ByteArray { - return context.assets.open("default_app_config.bin").readBytes() + return context.assets.open("default_app_config_android.bin").readBytes() } } From c2b01a36926ef55ef90b0f5d835eb803dcfa0922 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Wed, 18 Nov 2020 15:42:11 +0100 Subject: [PATCH 10/47] Fixed unittests for DefaultAppConfigSource --- .../appconfig/download/DefaultAppConfigSourceTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/DefaultAppConfigSourceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/DefaultAppConfigSourceTest.kt index 2ddfad5332b..bdb202ae6d9 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/DefaultAppConfigSourceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/download/DefaultAppConfigSourceTest.kt @@ -19,8 +19,8 @@ class DefaultAppConfigSourceTest : BaseIOTest() { @MockK private lateinit var assetManager: AssetManager private val testDir = File(IO_TEST_BASEDIR, this::class.java.simpleName) - private val configFile = File(testDir, "default_app_config.bin") - private val checksumFile = File(testDir, "default_app_config.sha256") + private val configFile = File(testDir, "default_app_config_android.bin") + private val checksumFile = File(testDir, "default_app_config_android.sha256") @BeforeEach fun setup() { @@ -28,8 +28,8 @@ class DefaultAppConfigSourceTest : BaseIOTest() { every { context.assets } returns assetManager - every { assetManager.open("default_app_config.bin") } answers { configFile.inputStream() } - every { assetManager.open("default_app_config.sha256") } answers { checksumFile.inputStream() } + every { assetManager.open("default_app_config_android.bin") } answers { configFile.inputStream() } + every { assetManager.open("default_app_config_android.sha256") } answers { checksumFile.inputStream() } testDir.mkdirs() testDir.exists() shouldBe true From bc59d9c27ddb9b077d5760f0598f4cd4d7d4f350 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Wed, 18 Nov 2020 16:58:01 +0100 Subject: [PATCH 11/47] Started to address some comments from the sneak preview PR Signed-off-by: Kolya Opahle --- ...iskLevelCalculationFragmentCWAViewModel.kt | 8 +-- .../DefaultDiagnosisKeyProvider.kt | 2 +- .../coronawarnapp/risk/DefaultRiskLevels.kt | 64 +++++++------------ .../rki/coronawarnapp/risk/RiskLevelTask.kt | 2 +- .../de/rki/coronawarnapp/risk/RiskLevels.kt | 5 +- .../windows/ExposureWindowsCalculationTest.kt | 8 ++- .../rki/coronawarnapp/risk/RiskLevelsTest.kt | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 41 insertions(+), 54 deletions(-) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index 3e0afe10007..a04183c6003 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -123,16 +123,18 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( fun startENFObserver() { launch { try { + val appConfig = appConfigProvider.getAppConfig() + var workState = riskScoreState.value!! val exposureWindows = enfClient.exposureWindows() val riskResultsPerWindow = exposureWindows.mapNotNull { window -> - riskLevels.calculateRisk(window)?.let { window to it } + riskLevels.calculateRisk(appConfig, window)?.let { window to it } }.toMap() - val aggregatedResult = riskLevels.aggregateResults(riskResultsPerWindow) + val aggregatedResult = riskLevels.aggregateResults(appConfig, riskResultsPerWindow) val riskAsString = "Level: ${RiskLevelRepository.getLastCalculatedScore()}\n" + "Last successful Level: " + @@ -147,8 +149,6 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( workState = workState.copy(riskScoreMsg = riskAsString) - val appConfig = appConfigProvider.getAppConfig() - val configAsString = "Transmission RiskLevel Multiplier: ${appConfig.transmissionRiskLevelMultiplier}\n" + "Minutes At Attenuation Filters: ${appConfig.minutesAtAttenuationFilters}\n" + diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt index ebf8523d7de..dd57c0b54c5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt @@ -27,7 +27,7 @@ class DefaultDiagnosisKeyProvider @Inject constructor( if (!googleAPIVersion.isAtLeast(GoogleAPIVersion.V15)) { // Actually this shouldn't happen - Timber.d("No key files submitted because client uses an old unsupported version") + Timber.e("No key files submitted because client uses an old unsupported version") return false } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index 07481acc9ce..47879a2dd4c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -8,7 +8,6 @@ import com.google.android.gms.nearby.exposurenotification.Infectiousness import com.google.android.gms.nearby.exposurenotification.ReportType import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.appconfig.download.ApplicationConfigurationInvalidException import de.rki.coronawarnapp.exception.RiskLevelCalculationException @@ -22,8 +21,6 @@ import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParamete import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.RiskLevelRepository import de.rki.coronawarnapp.util.TimeAndDateExtensions.millisecondsToHours -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject @@ -31,20 +28,7 @@ import javax.inject.Singleton typealias ProtoRiskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel @Singleton -class DefaultRiskLevels @Inject constructor( - private val appConfigProvider: AppConfigProvider -) : RiskLevels { - private var appConfig: ConfigData - - init { - runBlocking { - appConfig = appConfigProvider.getAppConfig() - } - - appConfigProvider.currentConfig - .onEach { if (it != null) appConfig = it } - } - +class DefaultRiskLevels @Inject constructor() : RiskLevels { override fun updateRepository(riskLevel: RiskLevel, time: Long) { val rollbackItems = mutableListOf() try { @@ -99,13 +83,13 @@ class DefaultRiskLevels @Inject constructor( } } - override fun isIncreasedRisk(exposureWindows: List): Boolean { + override fun isIncreasedRisk(appConfig: ConfigData, exposureWindows: List): Boolean { val riskResultsPerWindow = exposureWindows.mapNotNull { window -> - calculateRisk(window)?.let { window to it } + calculateRisk(appConfig, window)?.let { window to it } }.toMap() - val aggregatedResult = aggregateResults(riskResultsPerWindow) + val aggregatedResult = aggregateResults(appConfig, riskResultsPerWindow) return aggregatedResult.totalRiskLevel == ProtoRiskLevel.HIGH } @@ -168,26 +152,24 @@ class DefaultRiskLevels @Inject constructor( RiskLevelRepository.setRiskLevelScore(riskLevel) } - private fun dropDueToMinutesAtAttenuation( - exposureWindow: ExposureWindow, + private fun ExposureWindow.dropDueToMinutesAtAttenuation( attenuationFilters: List ) = attenuationFilters.any { attenuationFilter -> // Get total seconds at attenuation in exposure window - val secondsAtAttenuation = exposureWindow.scanInstances + val secondsAtAttenuation: Double = scanInstances .filter { attenuationFilter.attenuationRange.inRange(it.typicalAttenuationDb) } - .fold(0) { acc, scanInstance -> acc + scanInstance.secondsSinceLastScan } + .fold(.0) { acc, scanInstance -> acc + scanInstance.secondsSinceLastScan } val minutesAtAttenuation = secondsAtAttenuation / 60 return attenuationFilter.dropIfMinutesInRange.inRange(minutesAtAttenuation) } - private fun determineTransmissionRiskLevel( - exposureWindow: ExposureWindow, + private fun ExposureWindow.determineTransmissionRiskLevel( transmissionRiskLevelEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding ): Int { - val reportTypeOffset = when (exposureWindow.reportType) { + val reportTypeOffset = when (reportType) { ReportType.RECURSIVE -> transmissionRiskLevelEncoding .reportTypeOffsetRecursive ReportType.SELF_REPORT -> transmissionRiskLevelEncoding @@ -199,7 +181,7 @@ class DefaultRiskLevels @Inject constructor( else -> throw UnknownReportTypeException() } - val infectiousnessOffset = when (exposureWindow.infectiousness) { + val infectiousnessOffset = when (infectiousness) { Infectiousness.HIGH -> transmissionRiskLevelEncoding .infectiousnessOffsetHigh else -> transmissionRiskLevelEncoding @@ -217,12 +199,11 @@ class DefaultRiskLevels @Inject constructor( it.dropIfTrlInRange.inRange(transmissionRiskLevel) } - private fun determineWeightedSeconds( - exposureWindow: ExposureWindow, + private fun ExposureWindow.determineWeightedSeconds( minutesAtAttenuationWeight: List ): Double = - exposureWindow.scanInstances.fold(.0) { seconds, scanInstance -> - val weight = + scanInstances.fold(.0) { seconds, scanInstance -> + val weight: Double = minutesAtAttenuationWeight .filter { it.attenuationRange.inRange(scanInstance.typicalAttenuationDb) } .map { it.weight } @@ -240,9 +221,10 @@ class DefaultRiskLevels @Inject constructor( .firstOrNull() override fun calculateRisk( + appConfig: ConfigData, exposureWindow: ExposureWindow ): RiskResult? { - if (dropDueToMinutesAtAttenuation(exposureWindow, appConfig.minutesAtAttenuationFilters)) { + if (exposureWindow.dropDueToMinutesAtAttenuation(appConfig.minutesAtAttenuationFilters)) { Timber.d( "%s dropped due to minutes at attenuation filter", exposureWindow @@ -250,8 +232,7 @@ class DefaultRiskLevels @Inject constructor( return null } - val transmissionRiskLevel = determineTransmissionRiskLevel( - exposureWindow, + val transmissionRiskLevel: Int = exposureWindow.determineTransmissionRiskLevel( appConfig.transmissionRiskLevelEncoding ) @@ -264,7 +245,7 @@ class DefaultRiskLevels @Inject constructor( return null } - val transmissionRiskValue = + val transmissionRiskValue: Double = transmissionRiskLevel * appConfig.transmissionRiskLevelMultiplier Timber.d( @@ -273,10 +254,9 @@ class DefaultRiskLevels @Inject constructor( transmissionRiskValue ) - val weightedMinutes = determineWeightedSeconds( - exposureWindow, + val weightedMinutes: Double = exposureWindow.determineWeightedSeconds( appConfig.minutesAtAttenuationWeights - ) / 60 + ) / 60f Timber.d( "%s's weightedMinutes are: %s", @@ -284,7 +264,7 @@ class DefaultRiskLevels @Inject constructor( weightedMinutes ) - val normalizedTime = transmissionRiskValue * weightedMinutes + val normalizedTime: Double = transmissionRiskValue * weightedMinutes Timber.d( "%s's normalizedTime is: %s", @@ -312,6 +292,7 @@ class DefaultRiskLevels @Inject constructor( } override fun aggregateResults( + appConfig: ConfigData, exposureWindowsAndResult: Map ): AggregatedRiskResult { val uniqueDatesMillisSinceEpoch = exposureWindowsAndResult.keys @@ -324,7 +305,7 @@ class DefaultRiskLevels @Inject constructor( }" ) val exposureHistory = uniqueDatesMillisSinceEpoch.map { - aggregateRiskPerDate(it, exposureWindowsAndResult) + aggregateRiskPerDate(appConfig, it, exposureWindowsAndResult) } Timber.d("exposureHistory size: ${exposureHistory.size}") @@ -401,6 +382,7 @@ class DefaultRiskLevels @Inject constructor( .size private fun aggregateRiskPerDate( + appConfig: ConfigData, dateMillisSinceEpoch: Long, exposureWindowsAndResult: Map ): AggregatedRiskPerDateResult { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt index 960d30f494c..414ec6e9d86 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelTask.kt @@ -78,7 +78,7 @@ class RiskLevelTask @Inject constructor( UNKNOWN_RISK_OUTDATED_RESULTS_MANUAL } - isIncreasedRisk(getExposureWindows()).also { + isIncreasedRisk(configData, getExposureWindows()).also { checkCancel() } -> INCREASED_RISK diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt index 63c83ac7730..f741b8b6b50 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevels.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.risk.result.RiskResult @@ -16,7 +17,7 @@ interface RiskLevels { */ fun isActiveTracingTimeAboveThreshold(): Boolean - fun isIncreasedRisk(exposureWindows: List): Boolean + fun isIncreasedRisk(appConfig: ConfigData, exposureWindows: List): Boolean fun updateRepository( riskLevel: RiskLevel, @@ -24,10 +25,12 @@ interface RiskLevels { ) fun calculateRisk( + appConfig: ConfigData, exposureWindow: ExposureWindow ): RiskResult? fun aggregateResults( + appConfig: ConfigData, exposureWindowsAndResult: Map ): AggregatedRiskResult } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt index 41c26ebc228..f0de3266fa4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -102,7 +102,9 @@ class ExposureWindowsCalculationTest : BaseTest() { coEvery { appConfigProvider.getAppConfig() } returns testConfig every { appConfigProvider.currentConfig } returns flow { testConfig } logConfiguration(testConfig) - riskLevels = DefaultRiskLevels(appConfigProvider) + riskLevels = DefaultRiskLevels() + + val appConfig = appConfigProvider.getAppConfig() // 4 - Mock and log exposure windows val allExposureWindows = mutableListOf() @@ -116,12 +118,12 @@ class ExposureWindowsCalculationTest : BaseTest() { for (exposureWindow: ExposureWindow in exposureWindows) { logExposureWindow(exposureWindow, "➡➡ EXPOSURE WINDOW PASSED ➡➡", LogLevel.EXTENDED) - val riskResult = riskLevels.calculateRisk(exposureWindow) ?: continue + val riskResult = riskLevels.calculateRisk(appConfig, exposureWindow) ?: continue exposureWindowsAndResult[exposureWindow] = riskResult } debugLog("Exposure windows and result: ${exposureWindowsAndResult.size}") - val aggregatedRiskResult = riskLevels.aggregateResults(exposureWindowsAndResult) + val aggregatedRiskResult = riskLevels.aggregateResults(appConfig, exposureWindowsAndResult) debugLog( "\n" + comparisonDebugTable(aggregatedRiskResult, case), diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt index 3bc127ce14b..1e7b7ab6e41 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt @@ -23,7 +23,7 @@ class RiskLevelsTest : BaseTest() { coEvery { appConfigProvider.getAppConfig() } returns mockk() every { appConfigProvider.currentConfig } returns mockk() - riskLevels = DefaultRiskLevels(appConfigProvider) + riskLevels = DefaultRiskLevels() } @Test diff --git a/build.gradle b/build.gradle index 2851fded9a7..ff23445fd32 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0' + classpath 'com.android.tools.build:gradle:4.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.protobuf:protobuf-gradle-plugin:$protobufVersion" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4e1cc9db6b5..186b71557c5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a96bb279b4eb4eb419dd87c3d0994e263b9acbc3 Mon Sep 17 00:00:00 2001 From: chris-cwa <69595386+chris-cwa@users.noreply.github.com> Date: Wed, 18 Nov 2020 17:05:04 +0100 Subject: [PATCH 12/47] Removed enf v1 exposure summary and tokens (EXPOSUREAPP-3538) (#1644) * activate WindowExposure mode * extirpated tokens! * detekt, ktlint * fixed tests * - v1 methods * - get v1 exposure summary * - fixme * updated exposure summary repo * Adding mock dependencies that are required to instantiate the worker factories in our binding test. * Added numberOfDaysWithHighRisk and numberOfDaysWithLowRisk according to new tech spec * Transformed minimumDistinctEncountersForRisk and mostRecentDateForRisk into extension fun * make sure list of windows and aggregated result belong together * sending values for current UI * fixed ktlint * renamed ExposureResultStore * fixed tests Co-authored-by: Matthias Urhahn Co-authored-by: BMItter --- .../storage/ExposureSummaryDaoTest.kt | 76 ------------ .../test/api/ui/TestForAPIFragment.kt | 87 +++++++------- .../ui/TestRiskLevelCalculationFragment.kt | 2 +- ...iskLevelCalculationFragmentCWAViewModel.kt | 18 +-- .../download/DownloadDiagnosisKeysTask.kt | 20 +--- .../nearby/ExposureStateUpdateWorker.kt | 15 +-- .../InternalExposureNotificationClient.kt | 19 --- .../coronawarnapp/risk/DefaultRiskLevels.kt | 25 +++- .../coronawarnapp/risk/ExposureResultStore.kt | 16 +++ .../rki/coronawarnapp/storage/AppDatabase.kt | 1 - .../storage/ExposureSummaryRepository.kt | 70 ----------- .../de/rki/coronawarnapp/storage/LocalData.kt | 28 ----- .../storage/TracingRepository.kt | 31 ----- .../rki/coronawarnapp/submission/Symptoms.kt | 5 +- .../ui/main/home/HomeFragmentViewModel.kt | 1 - .../tracing/card/TracingCardStateProvider.kt | 6 +- .../details/RiskDetailsFragmentViewModel.kt | 1 - .../details/TracingDetailsStateProvider.kt | 6 +- .../util/di/ApplicationComponent.kt | 3 + .../src/main/res/values-bg/strings.xml | 2 - .../src/main/res/values-de/strings.xml | 2 - .../src/main/res/values-en/strings.xml | 2 - .../src/main/res/values-pl/strings.xml | 2 - .../src/main/res/values-ro/strings.xml | 2 - .../src/main/res/values-tr/strings.xml | 2 - .../src/main/res/values/strings.xml | 2 - .../windows/ExposureWindowsCalculationTest.kt | 4 +- .../rki/coronawarnapp/risk/RiskLevelsTest.kt | 3 +- .../storage/ExposureSummaryRepositoryTest.kt | 112 ------------------ .../util/worker/WorkerBinderTest.kt | 9 ++ 30 files changed, 120 insertions(+), 452 deletions(-) delete mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/storage/ExposureSummaryDaoTest.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/ExposureSummaryRepository.kt delete mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/ExposureSummaryRepositoryTest.kt diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/storage/ExposureSummaryDaoTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/storage/ExposureSummaryDaoTest.kt deleted file mode 100644 index 32df9c31bd5..00000000000 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/storage/ExposureSummaryDaoTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package de.rki.coronawarnapp.storage - -import android.content.Context -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -/** - * ExposureSummaryDao test. - */ -@RunWith(AndroidJUnit4::class) -class ExposureSummaryDaoTest { - private lateinit var dao: ExposureSummaryDao - private lateinit var db: AppDatabase - - @Before - fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = Room.inMemoryDatabaseBuilder( - context, AppDatabase::class.java - ).build() - dao = db.exposureSummaryDao() - } - - /** - * Test Create / Read DB operations. - */ - @Test - fun testCROperations() { - runBlocking { - val testEntity1 = ExposureSummaryEntity().apply { - this.daysSinceLastExposure = 1 - this.matchedKeyCount = 1 - this.maximumRiskScore = 1 - this.summationRiskScore = 1 - } - - val testEntity2 = ExposureSummaryEntity().apply { - this.daysSinceLastExposure = 2 - this.matchedKeyCount = 2 - this.maximumRiskScore = 2 - this.summationRiskScore = 2 - } - - assertThat(dao.getExposureSummaryEntities().isEmpty()).isTrue() - - val id1 = dao.insertExposureSummaryEntity(testEntity1) - var selectAll = dao.getExposureSummaryEntities() - var selectLast = dao.getLatestExposureSummary() - assertThat(dao.getExposureSummaryEntities().isEmpty()).isFalse() - assertThat(selectAll.size).isEqualTo(1) - assertThat(selectAll[0].id).isEqualTo(id1) - assertThat(selectLast).isNotNull() - assertThat(selectLast?.id).isEqualTo(id1) - - val id2 = dao.insertExposureSummaryEntity(testEntity2) - selectAll = dao.getExposureSummaryEntities() - selectLast = dao.getLatestExposureSummary() - assertThat(selectAll.isEmpty()).isFalse() - assertThat(selectAll.size).isEqualTo(2) - assertThat(selectLast).isNotNull() - assertThat(selectLast?.id).isEqualTo(id2) - } - } - - @After - fun closeDb() { - db.close() - } -} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt index 1f9d0818c3d..cd1af3acc41 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt @@ -16,7 +16,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient -import com.google.android.gms.nearby.exposurenotification.ExposureSummary +import com.google.android.gms.nearby.exposurenotification.ExposureWindow import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.google.android.material.snackbar.Snackbar import com.google.gson.Gson @@ -32,15 +32,12 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper import de.rki.coronawarnapp.receiver.ExposureStateUpdateReceiver import de.rki.coronawarnapp.risk.TimeVariables import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange import de.rki.coronawarnapp.sharing.ExposureSharingService import de.rki.coronawarnapp.storage.AppDatabase -import de.rki.coronawarnapp.storage.ExposureSummaryRepository -import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository import de.rki.coronawarnapp.test.menu.ui.TestMenuItem import de.rki.coronawarnapp.util.KeyFileHelper @@ -89,6 +86,10 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), AppInjector.component.enfClient } + private val exposureSummaryRepository by lazy { + AppInjector.component.exposureResultStore + } + private var myExposureKeysJSON: String? = null private var myExposureKeys: List? = mutableListOf() private var otherExposureKey: AppleLegacyKeyExchange.Key? = null @@ -96,8 +97,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), private lateinit var internalExposureNotificationPermissionHelper: InternalExposureNotificationPermissionHelper - private var token: String? = null - private lateinit var qrPager: ViewPager2 private lateinit var qrPagerAdapter: RecyclerView.Adapter @@ -108,8 +107,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - token = UUID.randomUUID().toString() - internalExposureNotificationPermissionHelper = InternalExposureNotificationPermissionHelper(this, this) @@ -168,8 +165,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), buttonRetrieveExposureSummary.setOnClickListener { vm.launch { - val summary = ExposureSummaryRepository.getExposureSummaryRepository() - .getExposureSummaryEntities().toString() + val summary = exposureSummaryRepository.exposureWindowEntities.toString() withContext(Dispatchers.Main) { showToast(summary) @@ -274,9 +270,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), if (null == otherExposureKey) { showToast("No other keys provided. Please fill the EditText with the JSON containing keys") } else { - token = UUID.randomUUID().toString() - LocalData.googleApiToken(token) - val appleKeyList = mutableListOf() for (key in otherExposureKeyList) { @@ -298,7 +291,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), val dir = File( File(requireContext().getExternalFilesDir(null), "key-export"), - token ?: "" + UUID.randomUUID().toString() ) dir.mkdirs() @@ -306,13 +299,13 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), lifecycleScope.launch { googleFileList = KeyFileHelper.asyncCreateExportFiles(appleFiles, dir) - Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys with token $token") + Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys") try { // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API enfClient.provideDiagnosisKeys( googleFileList ) - showToast("Provided ${appleKeyList.size} keys to Google API with token $token") + showToast("Provided ${appleKeyList.size} keys to Google API") } catch (e: Exception) { e.report(ExceptionCategory.EXPOSURENOTIFICATION) } @@ -321,15 +314,14 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), } private fun checkExposure() { - Timber.d("Check Exposure with token $token") + Timber.d("Check Exposure") lifecycleScope.launch { try { - val exposureSummary = - InternalExposureNotificationClient.asyncGetExposureSummary(token!!) + val exposureSummary = enfClient.exposureWindows() updateExposureSummaryDisplay(exposureSummary) - showToast("Updated Exposure Summary with token $token") - Timber.d("Received exposure with token $token from QR Code") + showToast("Updated Exposure Summary") + Timber.d("Received exposure from QR Code") Timber.i(exposureSummary.toString()) } catch (e: Exception) { e.report(ExceptionCategory.EXPOSURENOTIFICATION) @@ -337,32 +329,33 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), } } - private fun updateExposureSummaryDisplay(exposureSummary: ExposureSummary?) { - - binding.labelExposureSummaryMatchedKeyCount.text = getString( - R.string.test_api_body_matchedKeyCount, - (exposureSummary?.matchedKeyCount ?: "-").toString() - ) - - binding.labelExposureSummaryDaysSinceLastExposure.text = getString( - R.string.test_api_body_daysSinceLastExposure, - (exposureSummary?.daysSinceLastExposure ?: "-").toString() - ) - - binding.labelExposureSummaryMaximumRiskScore.text = getString( - R.string.test_api_body_maximumRiskScore, - (exposureSummary?.maximumRiskScore ?: "-").toString() - ) - - binding.labelExposureSummarySummationRiskScore.text = getString( - R.string.test_api_body_summation_risk, - (exposureSummary?.summationRiskScore ?: "-").toString() - ) - - binding.labelExposureSummaryAttenuation.text = getString( - R.string.test_api_body_attenuation, - (exposureSummary?.attenuationDurationsInMinutes?.joinToString() ?: "-").toString() - ) + private fun updateExposureSummaryDisplay(windows: List?) { + + // FIXME +// binding.labelExposureSummaryMatchedKeyCount.text = getString( +// R.string.test_api_body_matchedKeyCount, +// (exposureSummary?.matchedKeyCount ?: "-").toString() +// ) +// +// binding.labelExposureSummaryDaysSinceLastExposure.text = getString( +// R.string.test_api_body_daysSinceLastExposure, +// (exposureSummary?.daysSinceLastExposure ?: "-").toString() +// ) +// +// binding.labelExposureSummaryMaximumRiskScore.text = getString( +// R.string.test_api_body_maximumRiskScore, +// (exposureSummary?.maximumRiskScore ?: "-").toString() +// ) +// +// binding.labelExposureSummarySummationRiskScore.text = getString( +// R.string.test_api_body_summation_risk, +// (exposureSummary?.summationRiskScore ?: "-").toString() +// ) +// +// binding.labelExposureSummaryAttenuation.text = getString( +// R.string.test_api_body_attenuation, +// (exposureSummary?.attenuationDurationsInMinutes?.joinToString() ?: "-").toString() +// ) } private fun updateKeysDisplay() { diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt index f9834cdc792..9c8a948bbb5 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt @@ -79,7 +79,7 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le vm.apiKeysProvidedEvent.observe2(this) { event -> Toast.makeText( requireContext(), - "Provided ${event.keyCount} keys to Google API with token ${event.token}", + "Provided ${event.keyCount} keys to Google API", Toast.LENGTH_SHORT ).show() } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index 3e0afe10007..9f7b65e8d1a 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -102,7 +102,6 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( LocalData.lastCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw) LocalData.lastSuccessfullyCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw) LocalData.lastTimeDiagnosisKeysFromServerFetch(null) - LocalData.googleApiToken(null) } catch (e: Exception) { e.report(ExceptionCategory.INTERNAL) } @@ -176,14 +175,10 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( } data class DiagnosisKeyProvidedEvent( - val keyCount: Int, - val token: String + val keyCount: Int ) fun provideDiagnosisKey(transmissionNumber: Int, key: AppleLegacyKeyExchange.Key) { - val token = UUID.randomUUID().toString() - LocalData.googleApiToken(token) - val appleKeyList = mutableListOf() AppleLegacyKeyExchange.Key.newBuilder() @@ -200,23 +195,20 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( .build() ) - val dir = File(File(context.getExternalFilesDir(null), "key-export"), token) + val dir = File(File(context.getExternalFilesDir(null), "key-export"), UUID.randomUUID().toString()) dir.mkdirs() var googleFileList: List launch { googleFileList = KeyFileHelper.asyncCreateExportFiles(appleFiles, dir) - Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys with token $token") + Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys") try { // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API - enfClient.provideDiagnosisKeys( - googleFileList - ) + enfClient.provideDiagnosisKeys(googleFileList) apiKeysProvidedEvent.postValue( DiagnosisKeyProvidedEvent( - keyCount = appleFiles.size, - token = token + keyCount = appleFiles.size ) ) } catch (e: Exception) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt index 42b2008d5c8..f5d5bef1d35 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt @@ -26,7 +26,6 @@ import org.joda.time.Duration import org.joda.time.Instant import timber.log.Timber import java.util.Date -import java.util.UUID import javax.inject.Inject import javax.inject.Provider @@ -69,9 +68,8 @@ class DownloadDiagnosisKeysTask @Inject constructor( Timber.tag(TAG).d("Using $currentDate as current date in task.") /**************************************************** - * RETRIEVE TOKEN + * DOWNLOAD KEYS ****************************************************/ - val token = retrieveToken(rollbackItems) throwIfCancelled() // RETRIEVE RISK SCORE PARAMETERS @@ -113,10 +111,8 @@ class DownloadDiagnosisKeysTask @Inject constructor( ) Timber.tag(TAG).d("Attempting submission to ENF") - val isSubmissionSuccessful = enfClient.provideDiagnosisKeys( - keyFiles = availableKeyFiles - ) - Timber.tag(TAG).d("Diagnosis Keys provided (success=%s, token=%s)", isSubmissionSuccessful, token) + val isSubmissionSuccessful = enfClient.provideDiagnosisKeys(availableKeyFiles) + Timber.tag(TAG).d("Diagnosis Keys provided (success=%s)", isSubmissionSuccessful) internalProgress.send(Progress.ApiSubmissionFinished) throwIfCancelled() @@ -179,16 +175,6 @@ class DownloadDiagnosisKeysTask @Inject constructor( LocalData.lastTimeDiagnosisKeysFromServerFetch(currentDate) } - private fun retrieveToken(rollbackItems: MutableList): String { - val googleAPITokenForRollback = LocalData.googleApiToken() - rollbackItems.add { - LocalData.googleApiToken(googleAPITokenForRollback) - } - return UUID.randomUUID().toString().also { - LocalData.googleApiToken(it) - } - } - private fun noKeysFetchedToday(): Boolean { val currentDate = DateTime(timeStamper.nowUTC, DateTimeZone.UTC) val lastFetch = DateTime( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt index b0c92e94c97..f52f4075602 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt @@ -4,14 +4,12 @@ import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.google.android.gms.common.api.ApiException -import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory -import de.rki.coronawarnapp.exception.NoTokenException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.risk.RiskLevelTask -import de.rki.coronawarnapp.storage.ExposureSummaryRepository +import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.task.common.DefaultTaskRequest import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory @@ -20,19 +18,16 @@ import timber.log.Timber class ExposureStateUpdateWorker @AssistedInject constructor( @Assisted val context: Context, @Assisted workerParams: WorkerParameters, + private val exposureResultStore: ExposureResultStore, + private val enfClient: ENFClient, private val taskController: TaskController ) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { try { Timber.v("worker to persist exposure summary started") - val token = inputData.getString(ExposureNotificationClient.EXTRA_TOKEN) - ?: throw NoTokenException(IllegalArgumentException("no token was found in the intent")) - Timber.v("valid token $token retrieved") - InternalExposureNotificationClient - .asyncGetExposureSummary(token).also { - ExposureSummaryRepository.getExposureSummaryRepository() - .insertExposureSummaryEntity(it) + enfClient.exposureWindows().also { + exposureResultStore.exposureWindowEntities = Pair(it, null) Timber.v("exposure summary state updated: $it") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt index 53c0aa60066..2284f36d64c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt @@ -1,6 +1,5 @@ package de.rki.coronawarnapp.nearby -import com.google.android.gms.nearby.exposurenotification.ExposureSummary import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.risk.TimeVariables @@ -118,22 +117,4 @@ object InternalExposureNotificationClient { cont.resumeWithException(it) } } - - /** - * Retrieves the ExposureSummary object that matches the token from - * provideDiagnosisKeys() that you provide to the method. The ExposureSummary - * object provides a high-level overview of the exposure that a user has experienced. - * - * @param token - * @return - */ - suspend fun asyncGetExposureSummary(token: String): ExposureSummary = - suspendCoroutine { cont -> - exposureNotificationClient.getExposureSummary(token) - .addOnSuccessListener { - cont.resume(it) - }.addOnFailureListener { - cont.resumeWithException(it) - } - } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index 07481acc9ce..5104df0c88d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -22,6 +22,8 @@ import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParamete import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.RiskLevelRepository import de.rki.coronawarnapp.util.TimeAndDateExtensions.millisecondsToHours +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import org.joda.time.Instant @@ -32,7 +34,8 @@ typealias ProtoRiskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToR @Singleton class DefaultRiskLevels @Inject constructor( - private val appConfigProvider: AppConfigProvider + private val appConfigProvider: AppConfigProvider, + private val exposureResultStore: ExposureResultStore ) : RiskLevels { private var appConfig: ConfigData @@ -107,7 +110,19 @@ class DefaultRiskLevels @Inject constructor( val aggregatedResult = aggregateResults(riskResultsPerWindow) - return aggregatedResult.totalRiskLevel == ProtoRiskLevel.HIGH + exposureResultStore.exposureWindowEntities = Pair(exposureWindows, aggregatedResult) + + val highRisk = aggregatedResult.totalRiskLevel == ProtoRiskLevel.HIGH + + if (highRisk) { + internalMatchedKeyCount.value = aggregatedResult.totalMinimumDistinctEncountersWithHighRisk + internalDaysSinceLastExposure.value = aggregatedResult.numberOfDaysWithHighRisk + } else { + internalMatchedKeyCount.value = aggregatedResult.totalMinimumDistinctEncountersWithLowRisk + internalDaysSinceLastExposure.value = aggregatedResult.numberOfDaysWithLowRisk + } + + return highRisk } override fun isActiveTracingTimeAboveThreshold(): Boolean { @@ -470,5 +485,11 @@ class DefaultRiskLevels @Inject constructor( !maxExclusive && value.toDouble() > max -> false else -> true } + + private val internalMatchedKeyCount = MutableStateFlow(0) + val matchedKeyCount: Flow = internalMatchedKeyCount + + private val internalDaysSinceLastExposure = MutableStateFlow(0) + val daysSinceLastExposure: Flow = internalDaysSinceLastExposure } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt new file mode 100644 index 00000000000..d58a2848b30 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt @@ -0,0 +1,16 @@ +package de.rki.coronawarnapp.risk + +import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import javax.inject.Inject + +class ExposureResultStore @Inject constructor() { + + private var entities: Pair, AggregatedRiskResult?> = Pair(emptyList(), null) + + var exposureWindowEntities: Pair, AggregatedRiskResult?> + get() = entities + set(value) { + entities = value + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt index 66fc87e9d7b..1f37983a9d5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt @@ -62,7 +62,6 @@ abstract class AppDatabase : RoomDatabase() { val keyRepository = AppInjector.component.keyCacheRepository runBlocking { keyRepository.clear() } // TODO this is not nice TracingIntervalRepository.resetInstance() - ExposureSummaryRepository.resetInstance() } private fun buildDatabase(context: Context): AppDatabase { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/ExposureSummaryRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/ExposureSummaryRepository.kt deleted file mode 100644 index bd020294450..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/ExposureSummaryRepository.kt +++ /dev/null @@ -1,70 +0,0 @@ -package de.rki.coronawarnapp.storage - -import com.google.android.gms.nearby.exposurenotification.ExposureSummary -import de.rki.coronawarnapp.CoronaWarnApplication -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow - -class ExposureSummaryRepository(private val exposureSummaryDao: ExposureSummaryDao) { - companion object { - @Volatile - private var instance: ExposureSummaryRepository? = null - - private fun getInstance(exposureSummaryDao: ExposureSummaryDao) = - instance ?: synchronized(this) { - instance ?: ExposureSummaryRepository(exposureSummaryDao).also { instance = it } - } - - fun resetInstance() = synchronized(this) { - instance = null - } - - fun getExposureSummaryRepository(): ExposureSummaryRepository { - return getInstance( - AppDatabase.getInstance(CoronaWarnApplication.getAppContext()) - .exposureSummaryDao() - ) - } - - private val internalMatchedKeyCount = MutableStateFlow(0) - val matchedKeyCount: Flow = internalMatchedKeyCount - - private val internalDaysSinceLastExposure = MutableStateFlow(0) - val daysSinceLastExposure: Flow = internalDaysSinceLastExposure - } - - suspend fun getExposureSummaryEntities() = exposureSummaryDao.getExposureSummaryEntities() - .map { it.convertToExposureSummary() } - - suspend fun insertExposureSummaryEntity(exposureSummary: ExposureSummary) = - ExposureSummaryEntity().apply { - this.daysSinceLastExposure = exposureSummary.daysSinceLastExposure - this.matchedKeyCount = exposureSummary.matchedKeyCount - this.maximumRiskScore = exposureSummary.maximumRiskScore - this.summationRiskScore = exposureSummary.summationRiskScore - this.attenuationDurationsInMinutes = - exposureSummary.attenuationDurationsInMinutes.toTypedArray().toList() - }.run { - exposureSummaryDao.insertExposureSummaryEntity(this) - internalMatchedKeyCount.value = matchedKeyCount - internalDaysSinceLastExposure.value = daysSinceLastExposure - } - - suspend fun getLatestExposureSummary(token: String) { - if (InternalExposureNotificationClient.asyncIsEnabled()) - InternalExposureNotificationClient.asyncGetExposureSummary(token).also { - internalMatchedKeyCount.value = it.matchedKeyCount - internalDaysSinceLastExposure.value = it.daysSinceLastExposure - } - } - - private fun ExposureSummaryEntity.convertToExposureSummary() = - ExposureSummary.ExposureSummaryBuilder() - .setAttenuationDurations(this.attenuationDurationsInMinutes.toIntArray()) - .setDaysSinceLastExposure(this.daysSinceLastExposure) - .setMatchedKeyCount(this.matchedKeyCount) - .setMaximumRiskScore(this.maximumRiskScore) - .setSummationRiskScore(this.summationRiskScore) - .build() -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt index 04b08cdd10b..a0bac95d628 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt @@ -446,34 +446,6 @@ object LocalData { } } - /**************************************************** - * EXPOSURE NOTIFICATION DATA - ****************************************************/ - - /** - * Gets the last token that was used to provide the diagnosis keys to the Exposure Notification API - * - * @return UUID as string - */ - fun googleApiToken(): String? = getSharedPreferenceInstance().getString( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_string_google_api_token), - null - ) - - /** - * Sets the last token that was used to provide the diagnosis keys to the Exposure Notification API - * - * @param value UUID as string - */ - fun googleApiToken(value: String?) = getSharedPreferenceInstance().edit(true) { - putString( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_string_google_api_token), - value - ) - } - /**************************************************** * SETTINGS DATA ****************************************************/ 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 0658b106041..ada8c8b9b4c 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 @@ -2,8 +2,6 @@ package de.rki.coronawarnapp.storage import android.content.Context import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask -import de.rki.coronawarnapp.exception.ExceptionCategory -import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.risk.RiskLevelTask @@ -88,8 +86,6 @@ class TracingRepository @Inject constructor( * Regardless of whether the transactions where successful or not the * lastTimeDiagnosisKeysFetchedDate is updated. But the the value will only be updated after a * successful go through from the RetrievelDiagnosisKeysTransaction. - * - * @see RiskLevelRepository */ fun refreshDiagnosisKeys() { scope.launch { @@ -172,33 +168,6 @@ class TracingRepository @Inject constructor( } } - /** - * Exposure summary - * Refresh the following variables in TracingRepository - * - daysSinceLastExposure - * - matchedKeysCount - * - * @see TracingRepository - */ - fun refreshExposureSummary() { - scope.launch { - try { - val token = LocalData.googleApiToken() - if (token != null) { - ExposureSummaryRepository.getExposureSummaryRepository() - .getLatestExposureSummary(token) - } - Timber.tag(TAG).v("retrieved latest exposure summary from db") - } catch (e: Exception) { - e.report( - ExceptionCategory.EXPOSURENOTIFICATION, - TAG, - null - ) - } - } - } - fun refreshLastSuccessfullyCalculatedScore() { RiskLevelRepository.refreshLastSuccessfullyCalculatedScore() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt index 769df6264d8..0743eb1aaa0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt @@ -6,6 +6,9 @@ import org.joda.time.LocalDate @Parcelize data class Symptoms( + /** + * this is null if there are no symptoms or there is no information + */ val startOfSymptoms: StartOf?, val symptomIndication: Indication ) : Parcelable { @@ -36,7 +39,7 @@ data class Symptoms( companion object { val NO_INFO_GIVEN = Symptoms( - startOfSymptoms = null, // FIXME should this be null? + startOfSymptoms = null, symptomIndication = Indication.NO_INFORMATION ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt index ea111127b91..86c98ead527 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt @@ -103,7 +103,6 @@ class HomeFragmentViewModel @AssistedInject constructor( SubmissionRepository.refreshDeviceUIState() // TODO the ordering here is weird, do we expect these to run in sequence? tracingRepository.refreshRiskLevel() - tracingRepository.refreshExposureSummary() tracingRepository.refreshLastTimeDiagnosisKeysFetchedDate() tracingRepository.refreshActiveTracingDaysInRetentionPeriod() TimerHelper.checkManualKeyRetrievalTimer() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/card/TracingCardStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/card/TracingCardStateProvider.kt index 7e2f05d5beb..0997dc50f9b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/card/TracingCardStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/card/TracingCardStateProvider.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.ui.tracing.card import dagger.Reusable -import de.rki.coronawarnapp.storage.ExposureSummaryRepository +import de.rki.coronawarnapp.risk.DefaultRiskLevels import de.rki.coronawarnapp.storage.RiskLevelRepository import de.rki.coronawarnapp.storage.SettingsRepository import de.rki.coronawarnapp.storage.TracingRepository @@ -37,10 +37,10 @@ class TracingCardStateProvider @Inject constructor( tracingRepository.tracingProgress.onEach { Timber.v("tracingProgress: $it") }, - ExposureSummaryRepository.matchedKeyCount.onEach { + DefaultRiskLevels.matchedKeyCount.onEach { Timber.v("matchedKeyCount: $it") }, - ExposureSummaryRepository.daysSinceLastExposure.onEach { + DefaultRiskLevels.daysSinceLastExposure.onEach { Timber.v("daysSinceLastExposure: $it") }, tracingRepository.activeTracingDaysInRetentionPeriod.onEach { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/details/RiskDetailsFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/details/RiskDetailsFragmentViewModel.kt index 2075f82ed79..8ca9c951cf5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/details/RiskDetailsFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/details/RiskDetailsFragmentViewModel.kt @@ -36,7 +36,6 @@ class RiskDetailsFragmentViewModel @AssistedInject constructor( fun refreshData() { tracingRepository.refreshRiskLevel() - tracingRepository.refreshExposureSummary() tracingRepository.refreshLastTimeDiagnosisKeysFetchedDate() TimerHelper.checkManualKeyRetrievalTimer() tracingRepository.refreshActiveTracingDaysInRetentionPeriod() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/details/TracingDetailsStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/details/TracingDetailsStateProvider.kt index f07401a3268..f7265e75ca6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/details/TracingDetailsStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/details/TracingDetailsStateProvider.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.ui.tracing.details import dagger.Reusable -import de.rki.coronawarnapp.storage.ExposureSummaryRepository +import de.rki.coronawarnapp.risk.DefaultRiskLevels import de.rki.coronawarnapp.storage.RiskLevelRepository import de.rki.coronawarnapp.storage.SettingsRepository import de.rki.coronawarnapp.storage.TracingRepository @@ -30,8 +30,8 @@ class TracingDetailsStateProvider @Inject constructor( RiskLevelRepository.riskLevelScore, RiskLevelRepository.riskLevelScoreLastSuccessfulCalculated, tracingRepository.tracingProgress, - ExposureSummaryRepository.matchedKeyCount, - ExposureSummaryRepository.daysSinceLastExposure, + DefaultRiskLevels.matchedKeyCount, + DefaultRiskLevels.daysSinceLastExposure, tracingRepository.activeTracingDaysInRetentionPeriod, tracingRepository.lastTimeDiagnosisKeysFetched, backgroundModeStatus.isAutoModeEnabled, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt index af2cb89a00f..b9452f8b00b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt @@ -22,6 +22,7 @@ import de.rki.coronawarnapp.receiver.ReceiverBinder import de.rki.coronawarnapp.risk.RiskModule import de.rki.coronawarnapp.service.ServiceBinder import de.rki.coronawarnapp.storage.SettingsRepository +import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.submission.SubmissionModule import de.rki.coronawarnapp.submission.SubmissionTaskModule @@ -84,6 +85,8 @@ interface ApplicationComponent : AndroidInjector { val enfClient: ENFClient + val exposureResultStore: ExposureResultStore + val encryptedPreferencesFactory: EncryptedPreferencesFactory val errorResetTool: EncryptionErrorResetTool diff --git a/Corona-Warn-App/src/main/res/values-bg/strings.xml b/Corona-Warn-App/src/main/res/values-bg/strings.xml index 71900438526..2d1890d30f5 100644 --- a/Corona-Warn-App/src/main/res/values-bg/strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml @@ -24,8 +24,6 @@ "preference_timestamp_manual_diagnosis_keys_retrieval" - "preference_m_string_google_api_token" - "preference_background_job_enabled" "preference_mobile_data_enabled" diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml index 23ae7c9f347..1bcba1244b8 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -25,8 +25,6 @@ "preference_timestamp_manual_diagnosis_keys_retrieval" - "preference_m_string_google_api_token" - "preference_background_job_enabled" "preference_mobile_data_enabled" diff --git a/Corona-Warn-App/src/main/res/values-en/strings.xml b/Corona-Warn-App/src/main/res/values-en/strings.xml index 9d8ce585c34..1671da109a0 100644 --- a/Corona-Warn-App/src/main/res/values-en/strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/strings.xml @@ -24,8 +24,6 @@ "preference_timestamp_manual_diagnosis_keys_retrieval" - "preference_m_string_google_api_token" - "preference_background_job_enabled" "preference_mobile_data_enabled" diff --git a/Corona-Warn-App/src/main/res/values-pl/strings.xml b/Corona-Warn-App/src/main/res/values-pl/strings.xml index 7a02cc77e43..657e14803b0 100644 --- a/Corona-Warn-App/src/main/res/values-pl/strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml @@ -24,8 +24,6 @@ "preference_timestamp_manual_diagnosis_keys_retrieval" - "preference_m_string_google_api_token" - "preference_background_job_enabled" "preference_mobile_data_enabled" diff --git a/Corona-Warn-App/src/main/res/values-ro/strings.xml b/Corona-Warn-App/src/main/res/values-ro/strings.xml index 450e1d43d8e..1f52872f8b5 100644 --- a/Corona-Warn-App/src/main/res/values-ro/strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml @@ -24,8 +24,6 @@ "preference_timestamp_manual_diagnosis_keys_retrieval" - "preference_m_string_google_api_token" - "preference_background_job_enabled" "preference_mobile_data_enabled" diff --git a/Corona-Warn-App/src/main/res/values-tr/strings.xml b/Corona-Warn-App/src/main/res/values-tr/strings.xml index 6cfc04d3387..2a391bcd3bd 100644 --- a/Corona-Warn-App/src/main/res/values-tr/strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml @@ -24,8 +24,6 @@ "preference_timestamp_manual_diagnosis_keys_retrieval" - "preference_m_string_google_api_token" - "preference_background_job_enabled" "preference_mobile_data_enabled" diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index 35bfadc5544..403af63265f 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -25,8 +25,6 @@ "preference_timestamp_manual_diagnosis_keys_retrieval" - "preference_m_string_google_api_token" - "preference_background_job_enabled" "preference_mobile_data_enabled" diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt index 41c26ebc228..a2064a83503 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -16,6 +16,7 @@ import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonMinutesAtA import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonNormalizedTimeToRiskLevelMapping import de.rki.coronawarnapp.nearby.windows.entities.configuration.JsonTrlFilter import de.rki.coronawarnapp.risk.DefaultRiskLevels +import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.risk.result.RiskResult import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass @@ -48,6 +49,7 @@ class ExposureWindowsCalculationTest : BaseTest() { @MockK lateinit var appConfigProvider: AppConfigProvider @MockK lateinit var configData: ConfigData @MockK lateinit var timeStamper: TimeStamper + @MockK lateinit var exposureResultStore: ExposureResultStore private lateinit var riskLevels: DefaultRiskLevels private lateinit var testConfig: ConfigData @@ -102,7 +104,7 @@ class ExposureWindowsCalculationTest : BaseTest() { coEvery { appConfigProvider.getAppConfig() } returns testConfig every { appConfigProvider.currentConfig } returns flow { testConfig } logConfiguration(testConfig) - riskLevels = DefaultRiskLevels(appConfigProvider) + riskLevels = DefaultRiskLevels(appConfigProvider, exposureResultStore) // 4 - Mock and log exposure windows val allExposureWindows = mutableListOf() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt index 3bc127ce14b..26b3cd86f79 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelsTest.kt @@ -14,6 +14,7 @@ import testhelpers.BaseTest class RiskLevelsTest : BaseTest() { @MockK lateinit var appConfigProvider: AppConfigProvider + @MockK lateinit var exposureResultStore: ExposureResultStore private lateinit var riskLevels: DefaultRiskLevels @Before @@ -23,7 +24,7 @@ class RiskLevelsTest : BaseTest() { coEvery { appConfigProvider.getAppConfig() } returns mockk() every { appConfigProvider.currentConfig } returns mockk() - riskLevels = DefaultRiskLevels(appConfigProvider) + riskLevels = DefaultRiskLevels(appConfigProvider, exposureResultStore) } @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/ExposureSummaryRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/ExposureSummaryRepositoryTest.kt deleted file mode 100644 index b4a6ffd64b8..00000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/ExposureSummaryRepositoryTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -package de.rki.coronawarnapp.storage - -import com.google.android.gms.nearby.exposurenotification.ExposureSummary -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.unmockkAll -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import java.util.UUID - -/** - * ExposureSummaryRepository test. - */ -class ExposureSummaryRepositoryTest { - - @MockK - private lateinit var dao: ExposureSummaryDao - private lateinit var repository: ExposureSummaryRepository - - @Before - fun setUp() { - MockKAnnotations.init(this) - repository = ExposureSummaryRepository(dao) - - mockkObject(InternalExposureNotificationClient) - coEvery { InternalExposureNotificationClient.asyncGetExposureSummary(any()) } returns buildSummary() - coEvery { InternalExposureNotificationClient.asyncIsEnabled() } returns true - - coEvery { dao.getExposureSummaryEntities() } returns listOf() - coEvery { dao.getLatestExposureSummary() } returns null - coEvery { dao.insertExposureSummaryEntity(any()) } returns 0 - } - - /** - * Test DAO is called. - */ - @Test - fun testGet() { - runBlocking { - repository.getExposureSummaryEntities() - - coVerify { - dao.getExposureSummaryEntities() - } - } - } - - /** - * Test DAO is called. - */ - @Test - fun testGetLatest() { - runBlocking { - val token = UUID.randomUUID().toString() - repository.getLatestExposureSummary(token) - - coVerify { - InternalExposureNotificationClient.asyncGetExposureSummary(token) - } - } - } - - /** - * Test DAO is called. - */ - @Test - fun testInsert() { - val es = mockk() - every { es.attenuationDurationsInMinutes } returns intArrayOf(0) - every { es.daysSinceLastExposure } returns 1 - every { es.matchedKeyCount } returns 1 - every { es.maximumRiskScore } returns 0 - every { es.summationRiskScore } returns 0 - - runBlocking { - repository.insertExposureSummaryEntity(es) - - coVerify { - dao.insertExposureSummaryEntity(any()) - } - } - } - - private fun buildSummary( - maxRisk: Int = 0, - lowAttenuation: Int = 0, - midAttenuation: Int = 0, - highAttenuation: Int = 0 - ): ExposureSummary { - val intArray = IntArray(3) - intArray[0] = lowAttenuation - intArray[1] = midAttenuation - intArray[2] = highAttenuation - return ExposureSummary.ExposureSummaryBuilder() - .setMaximumRiskScore(maxRisk) - .setAttenuationDurations(intArray) - .build() - } - - @After - fun cleanUp() { - unmockkAll() - } -} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt index 468877bbaaa..88976e539ba 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt @@ -6,7 +6,9 @@ import dagger.Module import dagger.Provides import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationSender +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.playbook.Playbook +import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.util.di.AssistedInjectModule import io.github.classgraph.ClassGraph @@ -87,4 +89,11 @@ class MockProvider { @Provides fun taskController(): TaskController = mockk() + + // For ExposureStateUpdateWorker + @Provides + fun enfClient(): ENFClient = mockk() + + @Provides + fun exposureSummaryRepository(): ExposureResultStore = mockk() } From 65eac7bc75ee4021acf2b7016476e251cbb8a5e0 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Wed, 18 Nov 2020 18:43:32 +0100 Subject: [PATCH 13/47] Ide removed something it shouldn't have --- .../java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt index ad732899f2e..88976e539ba 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt @@ -12,6 +12,7 @@ import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.util.di.AssistedInjectModule import io.github.classgraph.ClassGraph +import io.kotest.matchers.collections.shouldContainAll import io.mockk.mockk import org.junit.jupiter.api.Test import testhelpers.BaseTest From ddff543f061bc056e77a86d8319947150172aaf3 Mon Sep 17 00:00:00 2001 From: BMItter Date: Wed, 18 Nov 2020 19:23:23 +0100 Subject: [PATCH 14/47] Adjusted quota limit for exposure window mode --- .../diagnosiskeyprovider/SubmissionQuota.kt | 4 +- .../SubmissionQuotaTest.kt | 47 +++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt index bf5ff885a0c..b671c3502ad 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuota.kt @@ -87,9 +87,9 @@ class SubmissionQuota @Inject constructor( companion object { @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) /** - * This quota changes when using ExposureWindow mode from 20 to 6 per day + * This quota should be 6 when using ExposureWindow * See: https://developers.google.com/android/exposure-notifications/release-notes */ - internal const val DEFAULT_QUOTA = 20 + internal const val DEFAULT_QUOTA = 6 } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuotaTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuotaTest.kt index c5d7238a84b..52c90fc86ad 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuotaTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/SubmissionQuotaTest.kt @@ -69,26 +69,25 @@ class SubmissionQuotaTest : BaseTest() { quota.consumeQuota(5) shouldBe true } - coVerify { enfData.currentQuota = 20 } + coVerify { enfData.currentQuota = 6 } // Reset to 20, then consumed 5 - testStorageCurrentQuota shouldBe 15 + testStorageCurrentQuota shouldBe 1 } @Test fun `quota consumption return true if quota was available`() { - testStorageCurrentQuota shouldBe 20 + testStorageCurrentQuota shouldBe 6 val quota = createQuota() runBlocking { - quota.consumeQuota(10) shouldBe true - quota.consumeQuota(10) shouldBe true - quota.consumeQuota(10) shouldBe false + quota.consumeQuota(3) shouldBe true + quota.consumeQuota(3) shouldBe true quota.consumeQuota(1) shouldBe false } - verify(exactly = 4) { timeStamper.nowUTC } + verify(exactly = 3) { timeStamper.nowUTC } } @Test @@ -97,7 +96,7 @@ class SubmissionQuotaTest : BaseTest() { runBlocking { quota.consumeQuota(0) shouldBe true - quota.consumeQuota(20) shouldBe true + quota.consumeQuota(6) shouldBe true quota.consumeQuota(0) shouldBe true quota.consumeQuota(1) shouldBe false } @@ -105,12 +104,12 @@ class SubmissionQuotaTest : BaseTest() { @Test fun `partial consumption is not possible`() { - testStorageCurrentQuota shouldBe 20 + testStorageCurrentQuota shouldBe 6 val quota = createQuota() runBlocking { - quota.consumeQuota(18) shouldBe true + quota.consumeQuota(4) shouldBe true quota.consumeQuota(1) shouldBe true quota.consumeQuota(2) shouldBe false } @@ -124,23 +123,23 @@ class SubmissionQuotaTest : BaseTest() { val timeTravelTarget = Instant.parse("2020-12-24T00:00:00.001Z") runBlocking { - quota.consumeQuota(20) shouldBe true - quota.consumeQuota(20) shouldBe false + quota.consumeQuota(6) shouldBe true + quota.consumeQuota(6) shouldBe false every { timeStamper.nowUTC } returns timeTravelTarget - quota.consumeQuota(20) shouldBe true + quota.consumeQuota(6) shouldBe true quota.consumeQuota(1) shouldBe false } - coVerify(exactly = 1) { enfData.currentQuota = 20 } + coVerify(exactly = 1) { enfData.currentQuota = 6 } verify(exactly = 4) { timeStamper.nowUTC } verify(exactly = 1) { enfData.lastQuotaResetAt = timeTravelTarget } } @Test fun `quota fill up is at midnight`() { - testStorageCurrentQuota = 20 + testStorageCurrentQuota = 6 testStorageLastQuotaReset = Instant.parse("2020-12-24T23:00:00.000Z") val startTime = Instant.parse("2020-12-24T23:59:59.998Z") every { timeStamper.nowUTC } returns startTime @@ -148,7 +147,7 @@ class SubmissionQuotaTest : BaseTest() { val quota = createQuota() runBlocking { - quota.consumeQuota(20) shouldBe true + quota.consumeQuota(6) shouldBe true quota.consumeQuota(1) shouldBe false every { timeStamper.nowUTC } returns startTime.plus(1) @@ -161,10 +160,10 @@ class SubmissionQuotaTest : BaseTest() { quota.consumeQuota(1) shouldBe true every { timeStamper.nowUTC } returns startTime.plus(4) - quota.consumeQuota(20) shouldBe false + quota.consumeQuota(6) shouldBe false every { timeStamper.nowUTC } returns startTime.plus(3).plus(Duration.standardDays(1)) - quota.consumeQuota(20) shouldBe true + quota.consumeQuota(6) shouldBe true } } @@ -175,26 +174,26 @@ class SubmissionQuotaTest : BaseTest() { runBlocking { every { timeStamper.nowUTC } returns startTime val quota = createQuota() - quota.consumeQuota(17) shouldBe true + quota.consumeQuota(3) shouldBe true } runBlocking { every { timeStamper.nowUTC } returns startTime.plus(Duration.standardDays(365)) val quota = createQuota() - quota.consumeQuota(20) shouldBe true + quota.consumeQuota(6) shouldBe true quota.consumeQuota(1) shouldBe false } runBlocking { every { timeStamper.nowUTC } returns startTime.plus(Duration.standardDays(365 * 2)) val quota = createQuota() - quota.consumeQuota(17) shouldBe true + quota.consumeQuota(3) shouldBe true } runBlocking { every { timeStamper.nowUTC } returns startTime.plus(Duration.standardDays(365 * 3)) val quota = createQuota() quota.consumeQuota(3) shouldBe true - quota.consumeQuota(17) shouldBe true + quota.consumeQuota(3) shouldBe true quota.consumeQuota(1) shouldBe false } } @@ -208,12 +207,12 @@ class SubmissionQuotaTest : BaseTest() { val quota = createQuota() runBlocking { - quota.consumeQuota(20) shouldBe true + quota.consumeQuota(6) shouldBe true quota.consumeQuota(1) shouldBe false // Go forward and get a reset every { timeStamper.nowUTC } returns startTime.plus(Duration.standardHours(1)) - quota.consumeQuota(20) shouldBe true + quota.consumeQuota(6) shouldBe true quota.consumeQuota(1) shouldBe false // Go backwards and don't gain a reset From d5772bb3bc026a59c98fd4cadf10ab5818b211c9 Mon Sep 17 00:00:00 2001 From: chris-cwa Date: Wed, 18 Nov 2020 19:50:05 +0100 Subject: [PATCH 15/47] use 1.8.x variants --- .../appconfig/mapping/ExposureDetectionConfigMapper.kt | 2 +- .../appconfig/mapping/KeyDownloadParametersMapper.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapper.kt index 9c0e3001e87..5110334a3cb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapper.kt @@ -11,7 +11,7 @@ import javax.inject.Inject @Reusable class ExposureDetectionConfigMapper @Inject constructor() : ExposureDetectionConfig.Mapper { override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): ExposureDetectionConfig { - val exposureParams = if (rawConfig.hasAndroidExposureDetectionParameters()) { + val exposureParams = if (rawConfig.hasExposureDetectionParameters()) { rawConfig.exposureDetectionParameters } else { null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/KeyDownloadParametersMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/KeyDownloadParametersMapper.kt index 04c5352105a..f1a5a2671e6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/KeyDownloadParametersMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/KeyDownloadParametersMapper.kt @@ -16,7 +16,7 @@ import javax.inject.Inject @Reusable class KeyDownloadParametersMapper @Inject constructor() : KeyDownloadConfig.Mapper { override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): KeyDownloadConfig { - val rawParameters = if (rawConfig.hasAndroidKeyDownloadParameters()) { + val rawParameters = if (rawConfig.hasKeyDownloadParameters()) { rawConfig.keyDownloadParameters } else { null From 802f4d6719106282d11f5d9bb1479d843e7d217b Mon Sep 17 00:00:00 2001 From: chris-cwa Date: Wed, 18 Nov 2020 19:54:29 +0100 Subject: [PATCH 16/47] fixed some test imports --- .../coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt | 1 + .../appconfig/mapping/ExposureDetectionConfigMapperTest.kt | 1 + .../coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt | 1 + .../appconfig/sources/remote/AppConfigServerTest.kt | 1 + .../nearby/windows/ExposureWindowsCalculationTest.kt | 1 - 5 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt index 3d71e8bcfc8..4f89f4d24eb 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.appconfig.mapping import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode +import de.rki.coronawarnapp.server.protocols.internal.AppConfig import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import de.rki.coronawarnapp.server.protocols.internal.v2.KeyDownloadParameters import io.kotest.matchers.shouldBe diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt index f414394224a..816b2d28b80 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.appconfig.mapping +import de.rki.coronawarnapp.server.protocols.internal.AppConfig import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import de.rki.coronawarnapp.server.protocols.internal.v2.ExposureDetectionParameters.ExposureDetectionParametersAndroid import io.kotest.matchers.shouldBe diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt index 6ffb5f59dc5..f90e0f1b8df 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.appconfig.sources.remote import android.content.Context import de.rki.coronawarnapp.appconfig.AppConfigModule +import de.rki.coronawarnapp.appconfig.download.AppConfigApiV2 import de.rki.coronawarnapp.environment.download.DownloadCDNModule import de.rki.coronawarnapp.http.HttpModule import io.kotest.matchers.shouldBe diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigServerTest.kt index 879ce9ddff1..9ae6713c24f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigServerTest.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.appconfig.sources.remote +import de.rki.coronawarnapp.appconfig.download.AppConfigApiV2 import de.rki.coronawarnapp.appconfig.internal.ApplicationConfigurationCorruptException import de.rki.coronawarnapp.appconfig.internal.ApplicationConfigurationInvalidException import de.rki.coronawarnapp.appconfig.internal.InternalConfigData diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt index a2064a83503..faff0a59c70 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -5,7 +5,6 @@ import com.google.android.gms.nearby.exposurenotification.ScanInstance import com.google.gson.Gson import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.ConfigData -import de.rki.coronawarnapp.appconfig.DefaultConfigData import de.rki.coronawarnapp.nearby.windows.entities.ExposureWindowsJsonInput import de.rki.coronawarnapp.nearby.windows.entities.cases.JsonScanInstance import de.rki.coronawarnapp.nearby.windows.entities.cases.JsonWindow From d87d8b334eafa300833386742a331f15472c5696 Mon Sep 17 00:00:00 2001 From: chris-cwa Date: Wed, 18 Nov 2020 20:33:10 +0100 Subject: [PATCH 17/47] fixed types --- .../appconfig/mapping/DownloadConfigMapperTest.kt | 2 +- .../appconfig/mapping/ExposureDetectionConfigMapperTest.kt | 2 +- .../nearby/windows/ExposureWindowsCalculationTest.kt | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt index 4f89f4d24eb..8eafdc7667a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt @@ -64,7 +64,7 @@ class DownloadConfigMapperTest : BaseTest() { @Test fun `if the protobuf data structures are null we return defaults`() { - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() .build() createInstance().map(rawConfig).apply { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt index 816b2d28b80..7c58d77c4b9 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt @@ -75,7 +75,7 @@ class ExposureDetectionConfigMapperTest : BaseTest() { @Test fun `if protobuf is missing the datastructure we return defaults`() { - val rawConfig = AppConfig.ApplicationConfiguration.newBuilder() + val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() .setMinRiskScore(1) .build() createInstance().map(rawConfig).apply { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt index faff0a59c70..8d6137958a2 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -5,6 +5,7 @@ import com.google.android.gms.nearby.exposurenotification.ScanInstance import com.google.gson.Gson import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.ConfigData +import de.rki.coronawarnapp.appconfig.internal.ConfigDataContainer import de.rki.coronawarnapp.nearby.windows.entities.ExposureWindowsJsonInput import de.rki.coronawarnapp.nearby.windows.entities.cases.JsonScanInstance import de.rki.coronawarnapp.nearby.windows.entities.cases.JsonWindow @@ -294,7 +295,7 @@ class ExposureWindowsCalculationTest : BaseTest() { private fun jsonToConfiguration(json: DefaultRiskCalculationConfiguration) { - testConfig = DefaultConfigData( + testConfig = ConfigDataContainer( serverTime = Instant.now(), localOffset = Duration.ZERO, mappedConfig = configData, From ea62e49f92d64c4c7b848a1b1dab1ae0cfc4fe83 Mon Sep 17 00:00:00 2001 From: chris-cwa Date: Wed, 18 Nov 2020 21:11:43 +0100 Subject: [PATCH 18/47] flow for aggregated result --- .../java/de/rki/coronawarnapp/risk/ExposureResultStore.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt index d58a2848b30..3c2c55c8c63 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt @@ -2,15 +2,21 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject class ExposureResultStore @Inject constructor() { + private val internalResult = MutableStateFlow(null) + val result: Flow = internalResult + private var entities: Pair, AggregatedRiskResult?> = Pair(emptyList(), null) var exposureWindowEntities: Pair, AggregatedRiskResult?> get() = entities set(value) { entities = value + internalResult.value = value.second } } From 92a675dcde6b1bb6f323bab358cdfc5b88206b6f Mon Sep 17 00:00:00 2001 From: chris-cwa Date: Wed, 18 Nov 2020 21:23:09 +0100 Subject: [PATCH 19/47] flow for windows --- .../java/de/rki/coronawarnapp/risk/ExposureResultStore.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt index 3c2c55c8c63..27cefe75ba7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt @@ -11,12 +11,16 @@ class ExposureResultStore @Inject constructor() { private val internalResult = MutableStateFlow(null) val result: Flow = internalResult + private val internalWindows = MutableStateFlow>(emptyList()) + val windows: Flow> = internalWindows + private var entities: Pair, AggregatedRiskResult?> = Pair(emptyList(), null) var exposureWindowEntities: Pair, AggregatedRiskResult?> get() = entities set(value) { entities = value + internalWindows.value = value.first internalResult.value = value.second } } From 4756b0c8f35d384667a1f27da2c48a65217f3b2c Mon Sep 17 00:00:00 2001 From: chris-cwa Date: Wed, 18 Nov 2020 21:40:09 +0100 Subject: [PATCH 20/47] flow a pair --- .../test/api/ui/TestForAPIFragment.kt | 3 ++- ...iskLevelCalculationFragmentCWAViewModel.kt | 6 +++++- .../nearby/ExposureStateUpdateWorker.kt | 2 +- .../coronawarnapp/risk/DefaultRiskLevels.kt | 2 +- .../coronawarnapp/risk/ExposureResultStore.kt | 19 +++---------------- .../mapping/DownloadConfigMapperTest.kt | 1 - .../ExposureDetectionConfigMapperTest.kt | 1 - 7 files changed, 12 insertions(+), 22 deletions(-) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt index cd1af3acc41..df78daec50b 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt @@ -48,6 +48,7 @@ import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.joda.time.DateTime @@ -165,7 +166,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), buttonRetrieveExposureSummary.setOnClickListener { vm.launch { - val summary = exposureSummaryRepository.exposureWindowEntities.toString() + val summary = exposureSummaryRepository.entities.first().first.toString() withContext(Dispatchers.Main) { showToast(summary) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index 9f7b65e8d1a..f521a36ac7a 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -12,6 +12,7 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.ENFClient +import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.risk.RiskLevel import de.rki.coronawarnapp.risk.RiskLevelTask import de.rki.coronawarnapp.risk.RiskLevels @@ -52,7 +53,8 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( private val taskController: TaskController, private val keyCacheRepository: KeyCacheRepository, private val appConfigProvider: AppConfigProvider, - tracingCardStateProvider: TracingCardStateProvider + tracingCardStateProvider: TracingCardStateProvider, + private val exposureResultStore: ExposureResultStore ) : CWAViewModel( dispatcherProvider = dispatcherProvider ) { @@ -99,6 +101,8 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( // Export File Reset keyCacheRepository.clear() + exposureResultStore.entities.value = Pair(emptyList(), null) + LocalData.lastCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw) LocalData.lastSuccessfullyCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw) LocalData.lastTimeDiagnosisKeysFromServerFetch(null) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt index f52f4075602..d5fee52d0c0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt @@ -27,7 +27,7 @@ class ExposureStateUpdateWorker @AssistedInject constructor( try { Timber.v("worker to persist exposure summary started") enfClient.exposureWindows().also { - exposureResultStore.exposureWindowEntities = Pair(it, null) + exposureResultStore.entities.value = Pair(it, null) Timber.v("exposure summary state updated: $it") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index e77c2647246..c8d817c8d2f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -110,7 +110,7 @@ class DefaultRiskLevels @Inject constructor( val aggregatedResult = aggregateResults(riskResultsPerWindow) - exposureResultStore.exposureWindowEntities = Pair(exposureWindows, aggregatedResult) + exposureResultStore.entities.value = Pair(exposureWindows, aggregatedResult) val highRisk = aggregatedResult.totalRiskLevel == ProtoRiskLevel.HIGH diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt index 27cefe75ba7..4e5923cbfc7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt @@ -2,25 +2,12 @@ package de.rki.coronawarnapp.risk import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.risk.result.AggregatedRiskResult -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject class ExposureResultStore @Inject constructor() { - private val internalResult = MutableStateFlow(null) - val result: Flow = internalResult - - private val internalWindows = MutableStateFlow>(emptyList()) - val windows: Flow> = internalWindows - - private var entities: Pair, AggregatedRiskResult?> = Pair(emptyList(), null) - - var exposureWindowEntities: Pair, AggregatedRiskResult?> - get() = entities - set(value) { - entities = value - internalWindows.value = value.first - internalResult.value = value.second - } + val entities = MutableStateFlow(Pair(emptyList(), null)) } + +typealias ExposureResult = Pair, AggregatedRiskResult?> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt index 8eafdc7667a..a048e1468ab 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/DownloadConfigMapperTest.kt @@ -1,7 +1,6 @@ package de.rki.coronawarnapp.appconfig.mapping import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode -import de.rki.coronawarnapp.server.protocols.internal.AppConfig import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import de.rki.coronawarnapp.server.protocols.internal.v2.KeyDownloadParameters import io.kotest.matchers.shouldBe diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt index 7c58d77c4b9..d93da32760a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt @@ -1,6 +1,5 @@ package de.rki.coronawarnapp.appconfig.mapping -import de.rki.coronawarnapp.server.protocols.internal.AppConfig import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import de.rki.coronawarnapp.server.protocols.internal.v2.ExposureDetectionParameters.ExposureDetectionParametersAndroid import io.kotest.matchers.shouldBe From 6f28aef9146442bf4e5c2bec44e24519a49c0871 Mon Sep 17 00:00:00 2001 From: BMItter Date: Wed, 18 Nov 2020 22:29:33 +0100 Subject: [PATCH 21/47] Adjusted misleading fun names --- .../diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt index 6ef70a9cddd..54c45dcf4d0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt @@ -51,7 +51,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { ) @Test - fun `key provision is used on older ENF versions`() { + fun `provide diagnosis keys with outdated ENF versions`() { coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns false val provider = createProvider() @@ -82,7 +82,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { } @Test - fun `quota is consumed silently`() { + fun `provide diagnosis key when quota is empty`() { coEvery { googleAPIVersion.isAtLeast(GoogleAPIVersion.V15) } returns true coEvery { submissionQuota.consumeQuota(any()) } returns false From feeabf0d42894ccf7e96e90ba2bc6ec68d11417d Mon Sep 17 00:00:00 2001 From: BMItter Date: Wed, 18 Nov 2020 23:04:50 +0100 Subject: [PATCH 22/47] fix multiple instances for ExposureResultStore --- .../main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt index 4e5923cbfc7..9e06e15d448 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt @@ -4,7 +4,9 @@ import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject +import javax.inject.Singleton +@Singleton class ExposureResultStore @Inject constructor() { val entities = MutableStateFlow(Pair(emptyList(), null)) From 541651ae7018c38fa033c23ebe81af9adbcc9d51 Mon Sep 17 00:00:00 2001 From: AlexanderAlferov <64849422+AlexanderAlferov@users.noreply.github.com> Date: Thu, 19 Nov 2020 14:35:02 +0300 Subject: [PATCH 23/47] Adjusted to new test cases (#1665) --- .../windows/ExposureWindowsCalculationTest.kt | 1 + .../nearby/windows/entities/cases/TestCase.kt | 4 + .../exposure-windows-risk-calculation.json | 98 +++++++++++++------ 3 files changed, 74 insertions(+), 29 deletions(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt index 8d6137958a2..496cbb0ff1b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -297,6 +297,7 @@ class ExposureWindowsCalculationTest : BaseTest() { testConfig = ConfigDataContainer( serverTime = Instant.now(), + cacheValidity = Duration.ZERO, localOffset = Duration.ZERO, mappedConfig = configData, identifier = "soup", diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt index fa8f45277f6..1ee966b3541 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/entities/cases/TestCase.kt @@ -15,6 +15,10 @@ data class TestCase( val expTotalMinimumDistinctEncountersWithLowRisk: Int, @SerializedName("expTotalRiskLevel") val expTotalRiskLevel: Int, + @SerializedName("expNumberOfDaysWithLowRisk") + val expNumberOfDaysWithLowRisk: Int, + @SerializedName("expNumberOfDaysWithHighRisk") + val expNumberOfDaysWithHighRisk: Int, @SerializedName("exposureWindows") val exposureWindows: List ) diff --git a/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json index 4338b8f94fc..2dba5b7a8e1 100644 --- a/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json +++ b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json @@ -77,7 +77,7 @@ ], "trlEncoding": { "infectiousnessOffsetStandard": 0, - "infectiousnessOffsetHigh": 4, + "infectiousnessOffsetHigh": 4, "reportTypeOffsetRecursive": 4, "reportTypeOffsetSelfReport": 3, "reportTypeOffsetConfirmedClinicalDiagnosis": 2, @@ -112,7 +112,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "keep Exposure Windows that match minutesAtAttenuationFilters (>= 10 minutes)", @@ -140,7 +142,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "drop Exposure Windows that do not match minutesAtAttenuationFilters (>= 73 dB)", @@ -168,7 +172,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "keep Exposure Windows that match minutesAtAttenuationFilters (< 73 dB)", @@ -196,7 +202,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "drop Exposure Windows that do not match trlFilters (<= 2)", @@ -224,7 +232,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "keep Exposure Windows that match trlFilters (> 2)", @@ -252,7 +262,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "identify Exposure Window as Low Risk based on normalizedTime (< 15)", @@ -286,8 +298,8 @@ "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, "expTotalMinimumDistinctEncountersWithHighRisk": 0, - "expNumberOfExposureWindowsWithLowRisk": 1, - "expNumberOfExposureWindowsWithHighRisk": 0 + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "identify Exposure Window as High Risk based on normalizedTime (>= 15)", @@ -321,8 +333,8 @@ "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, "expTotalMinimumDistinctEncountersWithHighRisk": 1, - "expNumberOfExposureWindowsWithLowRisk": 1, - "expNumberOfExposureWindowsWithHighRisk": 0 + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 1 }, { "description": "identify the most recent date with Low Risk", @@ -386,7 +398,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 3, "expAgeOfMostRecentDateWithLowRisk": 2, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 3, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "count Exposure Windows with same Date/TRL/CallibrationConfidence only once towards distinct encounters with Low Risk", @@ -432,7 +446,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "count Exposure Windows with same Date/TRL but different CallibrationConfidence separately towards distinct encounters with Low Risk", @@ -478,7 +494,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 2, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "count Exposure Windows with same Date/CallibrationConfidence but different TRL separately towards distinct encounters with Low Risk", @@ -524,7 +542,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 2, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "count Exposure Windows with same TRL/CallibrationConfidence but different Date separately towards distinct encounters with Low Risk", @@ -570,7 +590,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 2, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 2, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "determine High Risk in total if there are sufficient Exposure Windows with a Low Risk", @@ -634,7 +656,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 0 + "expTotalMinimumDistinctEncountersWithHighRisk": 0, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 1 }, { "description": "identify the most recent date with High Risk", @@ -698,7 +722,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 2, - "expTotalMinimumDistinctEncountersWithHighRisk": 3 + "expTotalMinimumDistinctEncountersWithHighRisk": 3, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 3 }, { "description": "count Exposure Windows with same Date/TRL/CallibrationConfidence only once towards distinct encounters with High Risk", @@ -744,7 +770,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 1 + "expTotalMinimumDistinctEncountersWithHighRisk": 1, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 1 }, { "description": "count Exposure Windows with same Date/TRL but different CallibrationConfidence separately towards distinct encounters with High Risk", @@ -790,7 +818,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 2 + "expTotalMinimumDistinctEncountersWithHighRisk": 2, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 1 }, { "description": "count Exposure Windows with same Date/CallibrationConfidence but different TRL separately towards distinct encounters with High Risk", @@ -836,7 +866,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 2 + "expTotalMinimumDistinctEncountersWithHighRisk": 2, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 1 }, { "description": "count Exposure Windows with same TRL/CallibrationConfidence but different Date separately towards distinct encounters with High Risk", @@ -882,7 +914,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 2 + "expTotalMinimumDistinctEncountersWithHighRisk": 2, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 2 }, { "description": "determine High Risk in total if there is at least one Exposure Window with High Risk", @@ -928,7 +962,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 1, "expAgeOfMostRecentDateWithLowRisk": 2, "expAgeOfMostRecentDateWithHighRisk": 1, - "expTotalMinimumDistinctEncountersWithHighRisk": 1 + "expTotalMinimumDistinctEncountersWithHighRisk": 1, + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 1 }, { "description": "handle empty set of Exposure Windows", @@ -937,7 +973,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, - "expAgeOfMostRecentDateWithHighRisk": null + "expAgeOfMostRecentDateWithHighRisk": null, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "handle empty set of Scan Instances (should never happen)", @@ -954,7 +992,9 @@ "expTotalMinimumDistinctEncountersWithLowRisk": 0, "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expAgeOfMostRecentDateWithLowRisk": null, - "expAgeOfMostRecentDateWithHighRisk": null + "expAgeOfMostRecentDateWithHighRisk": null, + "expNumberOfDaysWithLowRisk": 0, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "handle a typicalAttenuation of zero (should never happen)", @@ -983,8 +1023,8 @@ "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expNumberOfExposureWindowsWithLowRisk": 1, - "expNumberOfExposureWindowsWithHighRisk": 0 + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 }, { "description": "handle secondsSinceLastScan of zero (should never happen)", @@ -1018,8 +1058,8 @@ "expTotalMinimumDistinctEncountersWithHighRisk": 0, "expAgeOfMostRecentDateWithLowRisk": 1, "expAgeOfMostRecentDateWithHighRisk": null, - "expNumberOfExposureWindowsWithLowRisk": 1, - "expNumberOfExposureWindowsWithHighRisk": 0 + "expNumberOfDaysWithLowRisk": 1, + "expNumberOfDaysWithHighRisk": 0 } ] } From b74cec817e73e9d42bbdb025e4232f74916717c4 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Thu, 19 Nov 2020 12:54:08 +0100 Subject: [PATCH 24/47] Reverted gradle changes --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index ff23445fd32..2851fded9a7 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:4.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.protobuf:protobuf-gradle-plugin:$protobufVersion" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 186b71557c5..4e1cc9db6b5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From bad8ab7ef39b5f46cda3de3d3c5e2f333cb13bd5 Mon Sep 17 00:00:00 2001 From: Matthias Urhahn Date: Thu, 19 Nov 2020 13:14:32 +0100 Subject: [PATCH 25/47] Fix test regression. --- .../de/rki/coronawarnapp/nearby/modules/version/ENFVersion.kt | 1 + .../diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/ENFVersion.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/ENFVersion.kt index e0f3fec558f..3b071fc3269 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/ENFVersion.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/version/ENFVersion.kt @@ -12,5 +12,6 @@ interface ENFVersion { companion object { const val V16 = 16000000L + const val V15 = 15000000L } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt index d4e8aa99150..a563e4abbce 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt @@ -21,9 +21,6 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { @MockK lateinit var enfVersion: ENFVersion @MockK lateinit var submissionQuota: SubmissionQuota - @MockK - lateinit var submissionQuota: SubmissionQuota - private val exampleKeyFiles = listOf(File("file1"), File("file2")) @BeforeEach From 49996ef8a845140bf42cf1e5a00a7ffc8924071d Mon Sep 17 00:00:00 2001 From: Matthias Urhahn Date: Thu, 19 Nov 2020 13:16:21 +0100 Subject: [PATCH 26/47] Fix unexpected token confusing our exposure detection tracking logic. --- .../receiver/ExposureStateUpdateReceiver.kt | 25 ++++++++----------- .../ExposureStateUpdateReceiverTest.kt | 4 +-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt index 5fb17c90d21..72a049c1699 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt @@ -29,11 +29,8 @@ import javax.inject.Inject * new keys are processed. Then the [ExposureStateUpdateReceiver] will receive the corresponding action in its * [onReceive] function. * - * Inside this receiver no further action or calculation will be done but it is rather used to inform the - * [de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction] that the processing of the diagnosis keys is - * finished and the Exposure Summary can be retrieved in order to calculate a risk level to show to the user. - * - * @see de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction + * Inside this receiver no further action or calculation will be done but it is rather used to start + * a worker that launches the RiskLevelTask which then makes use of the new data this notifies us of. * */ class ExposureStateUpdateReceiver : BroadcastReceiver() { @@ -54,15 +51,13 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() { scope.launch(context = scope.coroutineContext) { try { - val token = intent.requireToken() - - trackDetection(token, action) + intent.getStringExtra(EXTRA_TOKEN)?.let { + Timber.tag(TAG).w("Received unknown token from ENF: %s", it) + } - val data = Data - .Builder() - .putString(EXTRA_TOKEN, token) - .build() + trackDetection(action) + val data = Data.Builder().build() OneTimeWorkRequest .Builder(ExposureStateUpdateWorker::class.java) .setInputData(data) @@ -78,13 +73,13 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() { } } - private fun trackDetection(token: String, action: String?) { + private fun trackDetection(action: String?) { when (action) { ACTION_EXPOSURE_STATE_UPDATED -> { - exposureDetectionTracker.finishExposureDetection(token, Result.UPDATED_STATE) + exposureDetectionTracker.finishExposureDetection(identifier = null, result = Result.UPDATED_STATE) } ACTION_EXPOSURE_NOT_FOUND -> { - exposureDetectionTracker.finishExposureDetection(token, Result.NO_MATCHES) + exposureDetectionTracker.finishExposureDetection(identifier = null, result = Result.NO_MATCHES) } else -> throw UnknownBroadcastException(action) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiverTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiverTest.kt index 9a703be6878..4bd4076caff 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiverTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiverTest.kt @@ -84,7 +84,7 @@ class ExposureStateUpdateReceiverTest : BaseTest() { scope.advanceUntilIdle() verifySequence { - exposureDetectionTracker.finishExposureDetection("token", TrackedExposureDetection.Result.UPDATED_STATE) + exposureDetectionTracker.finishExposureDetection(null, TrackedExposureDetection.Result.UPDATED_STATE) workManager.enqueue(any()) } } @@ -97,7 +97,7 @@ class ExposureStateUpdateReceiverTest : BaseTest() { scope.advanceUntilIdle() verifySequence { - exposureDetectionTracker.finishExposureDetection("token", TrackedExposureDetection.Result.NO_MATCHES) + exposureDetectionTracker.finishExposureDetection(null, TrackedExposureDetection.Result.NO_MATCHES) workManager.enqueue(any()) } } From 5bafefdd468eb4224b4cbbfefcfab41f1c4842ee Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Thu, 19 Nov 2020 13:22:35 +0100 Subject: [PATCH 27/47] Fixed failing tests --- .../appconfig/mapping/ExposureDetectionConfigMapperTest.kt | 1 - .../nearby/windows/ExposureWindowsCalculationTest.kt | 1 + .../ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt | 5 ++++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt index d93da32760a..f39e4f9dee0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ExposureDetectionConfigMapperTest.kt @@ -75,7 +75,6 @@ class ExposureDetectionConfigMapperTest : BaseTest() { @Test fun `if protobuf is missing the datastructure we return defaults`() { val rawConfig = AppConfigAndroid.ApplicationConfigurationAndroid.newBuilder() - .setMinRiskScore(1) .build() createInstance().map(rawConfig).apply { overallDetectionTimeout shouldBe Duration.standardMinutes(15) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt index 2bc5de6ff0d..ece66bfcb20 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/windows/ExposureWindowsCalculationTest.kt @@ -300,6 +300,7 @@ class ExposureWindowsCalculationTest : BaseTest() { testConfig = ConfigDataContainer( serverTime = Instant.now(), + cacheValidity = Duration.standardMinutes(5), localOffset = Duration.ZERO, mappedConfig = configData, identifier = "soup", diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt index 2eb25af0cdc..42cc57dc135 100644 --- a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt +++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModelTest.kt @@ -6,6 +6,7 @@ import com.google.android.gms.nearby.exposurenotification.ExposureNotificationCl import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.nearby.ENFClient +import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.risk.RiskLevels import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.ui.tracing.card.TracingCardStateProvider @@ -41,6 +42,7 @@ class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() { @MockK lateinit var taskController: TaskController @MockK lateinit var riskLevels: RiskLevels @MockK lateinit var appConfigProvider: AppConfigProvider + @MockK lateinit var exposureResultStore: ExposureResultStore @BeforeEach fun setup() { @@ -68,7 +70,8 @@ class TestRiskLevelCalculationFragmentCWAViewModelTest : BaseTest() { dispatcherProvider = TestDispatcherProvider, riskLevels = riskLevels, taskController = taskController, - appConfigProvider = appConfigProvider + appConfigProvider = appConfigProvider, + exposureResultStore = exposureResultStore ) @Test From 4f11a1a2f5776c1ee163b5a611d284494d4543e9 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Thu, 19 Nov 2020 13:47:48 +0100 Subject: [PATCH 28/47] Fixed lint issues from main merge --- .../src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt index c965b791d0c..587603da757 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt @@ -29,7 +29,7 @@ class ENFClient @Inject constructor( private val exposureWindowProvider: ExposureWindowProvider, private val exposureDetectionTracker: ExposureDetectionTracker, private val enfVersion: ENFVersion -) : DiagnosisKeyProvider, TracingStatus, ScanningSupport, ExposureWindowProvider, ENFVersion by enfVersion { +) : DiagnosisKeyProvider, TracingStatus, ScanningSupport, ExposureWindowProvider, ENFVersion by enfVersion { // TODO Remove this once we no longer need direct access to the ENF Client, // i.e. in **[InternalExposureNotificationClient]** From 096598541d1052176148b892308a18c724c73049 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Thu, 19 Nov 2020 14:38:33 +0100 Subject: [PATCH 29/47] Adressed some of the new comments --- .../test/api/ui/TestForAPIFragment.kt | 2 +- ...iskLevelCalculationFragmentCWAViewModel.kt | 3 +- .../nearby/ExposureStateUpdateWorker.kt | 5 +- .../coronawarnapp/risk/DefaultRiskLevels.kt | 46 +++++++++++-------- .../coronawarnapp/risk/ExposureResultStore.kt | 12 ++++- .../coronawarnapp/risk/result/RiskResult.kt | 1 - .../rki/coronawarnapp/update/UpdateChecker.kt | 4 +- 7 files changed, 44 insertions(+), 29 deletions(-) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt index df78daec50b..d4e224e12f9 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt @@ -166,7 +166,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), buttonRetrieveExposureSummary.setOnClickListener { vm.launch { - val summary = exposureSummaryRepository.entities.first().first.toString() + val summary = exposureSummaryRepository.entities.first().exposureWindows.toString() withContext(Dispatchers.Main) { showToast(summary) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index 70aec69b8c1..67d68216e20 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -12,6 +12,7 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.ENFClient +import de.rki.coronawarnapp.risk.ExposureResult import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.risk.RiskLevel import de.rki.coronawarnapp.risk.RiskLevelTask @@ -110,7 +111,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( // Export File Reset keyCacheRepository.clear() - exposureResultStore.entities.value = Pair(emptyList(), null) + exposureResultStore.entities.value = ExposureResult(emptyList(), null) LocalData.lastCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw) LocalData.lastSuccessfullyCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt index 92fe1c96528..f0f0ed5c5ff 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ExposureStateUpdateWorker.kt @@ -8,6 +8,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.risk.ExposureResult import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.risk.RiskLevelTask import de.rki.coronawarnapp.task.TaskController @@ -26,8 +27,8 @@ class ExposureStateUpdateWorker @AssistedInject constructor( override suspend fun doWork(): Result { try { Timber.v("worker to persist exposure summary started") - enfClient.exposureWindows().also { - exposureResultStore.entities.value = Pair(it, null) + enfClient.exposureWindows().let { + exposureResultStore.entities.value = ExposureResult(it, null) Timber.v("exposure summary state updated: $it") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index 9e542ee8e6d..e091c66cf61 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -95,7 +95,7 @@ class DefaultRiskLevels @Inject constructor( val aggregatedResult = aggregateResults(appConfig, riskResultsPerWindow) - exposureResultStore.entities.value = Pair(exposureWindows, aggregatedResult) + exposureResultStore.entities.value = ExposureResult(exposureWindows, aggregatedResult) val highRisk = aggregatedResult.totalRiskLevel == ProtoRiskLevel.HIGH @@ -227,7 +227,7 @@ class DefaultRiskLevels @Inject constructor( private fun determineRiskLevel( normalizedTime: Double, timeToRiskLevelMapping: List - ) = + ): ProtoRiskLevel? = timeToRiskLevelMapping .filter { it.normalizedTimeRange.inRange(normalizedTime) } .map { it.riskLevel } @@ -285,7 +285,7 @@ class DefaultRiskLevels @Inject constructor( normalizedTime ) - val riskLevel = determineRiskLevel( + val riskLevel: ProtoRiskLevel? = determineRiskLevel( normalizedTime, appConfig.normalizedTimePerExposureWindowToRiskLevelMapping ) @@ -301,7 +301,11 @@ class DefaultRiskLevels @Inject constructor( riskLevel ) - return RiskResult(transmissionRiskLevel, normalizedTime, riskLevel) + return RiskResult( + transmissionRiskLevel = transmissionRiskLevel, + normalizedTime = normalizedTime, + riskLevel = riskLevel + ) } override fun aggregateResults( @@ -313,9 +317,7 @@ class DefaultRiskLevels @Inject constructor( .toSet() Timber.d( - "uniqueDates: ${ - TextUtils.join(System.lineSeparator(), uniqueDatesMillisSinceEpoch) - }" + "uniqueDates: %s", { TextUtils.join(System.lineSeparator(), uniqueDatesMillisSinceEpoch) } ) val exposureHistory = uniqueDatesMillisSinceEpoch.map { aggregateRiskPerDate(appConfig, it, exposureWindowsAndResult) @@ -375,13 +377,13 @@ class DefaultRiskLevels @Inject constructor( Timber.d("numberOfDaysWithHighRisk: $numberOfDaysWithHighRisk") return AggregatedRiskResult( - totalRiskLevel, - totalMinimumDistinctEncountersWithLowRisk, - totalMinimumDistinctEncountersWithHighRisk, - mostRecentDateWithLowRisk, - mostRecentDateWithHighRisk, - numberOfDaysWithLowRisk, - numberOfDaysWithHighRisk + totalRiskLevel = totalRiskLevel, + totalMinimumDistinctEncountersWithLowRisk = totalMinimumDistinctEncountersWithLowRisk, + totalMinimumDistinctEncountersWithHighRisk = totalMinimumDistinctEncountersWithHighRisk, + mostRecentDateWithLowRisk = mostRecentDateWithLowRisk, + mostRecentDateWithHighRisk = mostRecentDateWithHighRisk, + numberOfDaysWithLowRisk = numberOfDaysWithLowRisk, + numberOfDaysWithHighRisk = numberOfDaysWithHighRisk ) } @@ -437,10 +439,10 @@ class DefaultRiskLevels @Inject constructor( Timber.d("minimumDistinctEncountersWithHighRisk: $minimumDistinctEncountersWithHighRisk") return AggregatedRiskPerDateResult( - dateMillisSinceEpoch, - riskLevel, - minimumDistinctEncountersWithLowRisk, - minimumDistinctEncountersWithHighRisk + dateMillisSinceEpoch = dateMillisSinceEpoch, + riskLevel = riskLevel, + minimumDistinctEncountersWithLowRisk = minimumDistinctEncountersWithLowRisk, + minimumDistinctEncountersWithHighRisk = minimumDistinctEncountersWithHighRisk ) } @@ -454,8 +456,12 @@ class DefaultRiskLevels @Inject constructor( private val TAG = DefaultRiskLevels::class.java.simpleName private const val DECIMAL_MULTIPLIER = 100 - class NormalizedTimePerExposureWindowToRiskLevelMappingMissingException : Exception() - class UnknownReportTypeException : Exception() + class NormalizedTimePerExposureWindowToRiskLevelMappingMissingException : Exception( + "Failed to map the normalized Time to a Risk Level" + ) + class UnknownReportTypeException : Exception( + "The Report Type returned by the ENF is not known" + ) private fun RiskCalculationParametersOuterClass.Range.inRange(value: T): Boolean = when { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt index 9e06e15d448..dd12361da67 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/ExposureResultStore.kt @@ -9,7 +9,15 @@ import javax.inject.Singleton @Singleton class ExposureResultStore @Inject constructor() { - val entities = MutableStateFlow(Pair(emptyList(), null)) + val entities = MutableStateFlow( + ExposureResult( + exposureWindows = emptyList(), + aggregatedRiskResult = null + ) + ) } -typealias ExposureResult = Pair, AggregatedRiskResult?> +data class ExposureResult( + val exposureWindows: List, + val aggregatedRiskResult: AggregatedRiskResult? +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/RiskResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/RiskResult.kt index e2b5c582b34..ee04a41efb0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/RiskResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/result/RiskResult.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.risk.result import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass -// TODO("Adjust Types") data class RiskResult( val transmissionRiskLevel: Int, val normalizedTime: Double, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt index ce5c756b765..695e297a1d7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt @@ -70,11 +70,11 @@ class UpdateChecker(private val activity: LauncherActivity) { val minVersionFromServer = cwaAppConfig.minVersionCode - Timber.e( + Timber.d( "minVersionFromServer:%s", minVersionFromServer ) - Timber.e("Current app version:%s", BuildConfig.VERSION_CODE) + Timber.d("Current app version:%s", BuildConfig.VERSION_CODE) val needsImmediateUpdate = VersionComparator.isVersionOlder( BuildConfig.VERSION_CODE.toLong(), From 07ae619b7bbd7c344c5cc13f3fcfbc5f51ec388a Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Thu, 19 Nov 2020 14:46:09 +0100 Subject: [PATCH 30/47] linting --- .../coronawarnapp/risk/DefaultRiskLevels.kt | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index e091c66cf61..599fb9b92aa 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -238,10 +238,7 @@ class DefaultRiskLevels @Inject constructor( exposureWindow: ExposureWindow ): RiskResult? { if (exposureWindow.dropDueToMinutesAtAttenuation(appConfig.minutesAtAttenuationFilters)) { - Timber.d( - "%s dropped due to minutes at attenuation filter", - exposureWindow - ) + Timber.d("%s dropped due to minutes at attenuation filter", exposureWindow) return null } @@ -261,29 +258,17 @@ class DefaultRiskLevels @Inject constructor( val transmissionRiskValue: Double = transmissionRiskLevel * appConfig.transmissionRiskLevelMultiplier - Timber.d( - "%s's transmissionRiskValue is: %s", - exposureWindow, - transmissionRiskValue - ) + Timber.d("%s's transmissionRiskValue is: %s", exposureWindow, transmissionRiskValue) val weightedMinutes: Double = exposureWindow.determineWeightedSeconds( appConfig.minutesAtAttenuationWeights ) / 60f - Timber.d( - "%s's weightedMinutes are: %s", - exposureWindow, - weightedMinutes - ) + Timber.d("%s's weightedMinutes are: %s", exposureWindow, weightedMinutes) val normalizedTime: Double = transmissionRiskValue * weightedMinutes - Timber.d( - "%s's normalizedTime is: %s", - exposureWindow, - normalizedTime - ) + Timber.d("%s's normalizedTime is: %s", exposureWindow, normalizedTime) val riskLevel: ProtoRiskLevel? = determineRiskLevel( normalizedTime, @@ -295,11 +280,7 @@ class DefaultRiskLevels @Inject constructor( throw NormalizedTimePerExposureWindowToRiskLevelMappingMissingException() } - Timber.d( - "%s's riskLevel is: %s", - exposureWindow, - riskLevel - ) + Timber.d("%s's riskLevel is: %s", exposureWindow, riskLevel) return RiskResult( transmissionRiskLevel = transmissionRiskLevel, From 895a58e9d72a514379ac748f7e0936e3a6033302 Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Thu, 19 Nov 2020 15:13:51 +0100 Subject: [PATCH 31/47] check if rawConfig contains riskCalculationParameters Signed-off-by: Kolya Opahle --- .../mapping/ExposureWindowRiskCalculationConfigMapper.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt index e64fdc2bad1..ab55b97ee82 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.appconfig.mapping import dagger.Reusable import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig +import de.rki.coronawarnapp.appconfig.internal.ApplicationConfigurationInvalidException import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass import javax.inject.Inject @@ -11,7 +12,14 @@ class ExposureWindowRiskCalculationConfigMapper @Inject constructor() : ExposureWindowRiskCalculationConfig.Mapper { override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): ExposureWindowRiskCalculationConfig { + if (!rawConfig.hasRiskCalculationParameters()) { + throw ApplicationConfigurationInvalidException( + message = "Risk Calculation Parameters are missing" + ) + } + val riskCalculationParameters = rawConfig.riskCalculationParameters + return ExposureWindowRiskCalculationContainer( minutesAtAttenuationFilters = riskCalculationParameters .minutesAtAttenuationFiltersList, From a15216990e05a9c1719b39604904d8a4e17a952a Mon Sep 17 00:00:00 2001 From: Kolya Opahle Date: Thu, 19 Nov 2020 16:29:25 +0100 Subject: [PATCH 32/47] switched typical to minimum attenuation in risk calculation --- .../coronawarnapp/risk/DefaultRiskLevels.kt | 4 +- .../exposure-windows-risk-calculation.json | 312 +++++++++--------- 2 files changed, 158 insertions(+), 158 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt index 599fb9b92aa..49cd92cd52a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt @@ -171,7 +171,7 @@ class DefaultRiskLevels @Inject constructor( attenuationFilters.any { attenuationFilter -> // Get total seconds at attenuation in exposure window val secondsAtAttenuation: Double = scanInstances - .filter { attenuationFilter.attenuationRange.inRange(it.typicalAttenuationDb) } + .filter { attenuationFilter.attenuationRange.inRange(it.minAttenuationDb) } .fold(.0) { acc, scanInstance -> acc + scanInstance.secondsSinceLastScan } val minutesAtAttenuation = secondsAtAttenuation / 60 @@ -218,7 +218,7 @@ class DefaultRiskLevels @Inject constructor( scanInstances.fold(.0) { seconds, scanInstance -> val weight: Double = minutesAtAttenuationWeight - .filter { it.attenuationRange.inRange(scanInstance.typicalAttenuationDb) } + .filter { it.attenuationRange.inRange(scanInstance.minAttenuationDb) } .map { it.weight } .firstOrNull() ?: .0 seconds + scanInstance.secondsSinceLastScan * weight diff --git a/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json index 2dba5b7a8e1..405aa42953a 100644 --- a/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json +++ b/Corona-Warn-App/src/test/resources/exposure-windows-risk-calculation.json @@ -96,13 +96,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 299 } ] @@ -126,13 +126,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -156,13 +156,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 73, - "minAttenuation": 25, + "minAttenuation": 73, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 73, - "minAttenuation": 25, + "minAttenuation": 73, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -186,13 +186,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 72, - "minAttenuation": 25, + "minAttenuation": 72, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 72, - "minAttenuation": 25, + "minAttenuation": 72, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -216,13 +216,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -246,13 +246,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -276,18 +276,18 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 299 } ] @@ -311,18 +311,18 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -346,13 +346,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -364,13 +364,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -382,13 +382,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -412,13 +412,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -430,13 +430,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -460,13 +460,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -478,13 +478,13 @@ "calibrationConfidence": 1, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -508,13 +508,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -526,13 +526,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -556,13 +556,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -574,13 +574,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -604,13 +604,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -622,13 +622,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -640,13 +640,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -670,13 +670,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -688,13 +688,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -706,13 +706,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -736,13 +736,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -754,13 +754,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -784,13 +784,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -802,13 +802,13 @@ "calibrationConfidence": 1, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -832,13 +832,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -850,13 +850,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -880,13 +880,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -898,13 +898,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -928,13 +928,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -946,13 +946,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 }, { - "typicalAttenuation": 30, - "minAttenuation": 25, + "minAttenuation": 30, + "typicalAttenuation": 25, "secondsSinceLastScan": 420 } ] @@ -997,7 +997,7 @@ "expNumberOfDaysWithHighRisk": 0 }, { - "description": "handle a typicalAttenuation of zero (should never happen)", + "description": "handle a typicalAttenuation: of zero (should never happen)", "exposureWindows": [ { "ageInDays": 1, @@ -1006,13 +1006,13 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 0, - "minAttenuation": 25, + "minAttenuation": 0, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 70, - "minAttenuation": 25, + "minAttenuation": 70, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -1036,18 +1036,18 @@ "calibrationConfidence": 0, "scanInstances": [ { - "typicalAttenuation": 70, - "minAttenuation": 25, + "minAttenuation": 70, + "typicalAttenuation": 25, "secondsSinceLastScan": 0 }, { - "typicalAttenuation": 70, - "minAttenuation": 25, + "minAttenuation": 70, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 }, { - "typicalAttenuation": 70, - "minAttenuation": 25, + "minAttenuation": 70, + "typicalAttenuation": 25, "secondsSinceLastScan": 300 } ] @@ -1062,4 +1062,4 @@ "expNumberOfDaysWithHighRisk": 0 } ] -} +} \ No newline at end of file From 967fd5d812c35d4a0fcc4bd7878f61d3e65b4a71 Mon Sep 17 00:00:00 2001 From: chris-cwa <69595386+chris-cwa@users.noreply.github.com> Date: Thu, 19 Nov 2020 17:42:48 +0100 Subject: [PATCH 33/47] Inject enf client and exposure result store (#1672) * inject enf client and exposure result store * Fix klint. Co-authored-by: Matthias Urhahn --- .../test/api/ui/TestForAPIFragment.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt index d4e224e12f9..1de6e298ada 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt @@ -32,8 +32,10 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper import de.rki.coronawarnapp.receiver.ExposureStateUpdateReceiver +import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.risk.TimeVariables import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange import de.rki.coronawarnapp.sharing.ExposureSharingService @@ -41,7 +43,6 @@ import de.rki.coronawarnapp.storage.AppDatabase import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository import de.rki.coronawarnapp.test.menu.ui.TestMenuItem import de.rki.coronawarnapp.util.KeyFileHelper -import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy @@ -64,6 +65,8 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), InternalExposureNotificationPermissionHelper.Callback, AutoInject { @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + @Inject lateinit var enfClient: ENFClient + @Inject lateinit var exposureResultStore: ExposureResultStore private val vm: TestForApiFragmentViewModel by cwaViewModels { viewModelFactory } companion object { @@ -83,14 +86,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), } } - private val enfClient by lazy { - AppInjector.component.enfClient - } - - private val exposureSummaryRepository by lazy { - AppInjector.component.exposureResultStore - } - private var myExposureKeysJSON: String? = null private var myExposureKeys: List? = mutableListOf() private var otherExposureKey: AppleLegacyKeyExchange.Key? = null @@ -166,7 +161,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), buttonRetrieveExposureSummary.setOnClickListener { vm.launch { - val summary = exposureSummaryRepository.entities.first().exposureWindows.toString() + val summary = exposureResultStore.entities.first().exposureWindows.toString() withContext(Dispatchers.Main) { showToast(summary) From df8d0057cfd18c38f1c8c3d00d15bb04a50e3cb2 Mon Sep 17 00:00:00 2001 From: BMItter <46747780+BMItter@users.noreply.github.com> Date: Thu, 19 Nov 2020 19:16:28 +0100 Subject: [PATCH 34/47] ENFv2 - Refactor testmenu to use exposure window (EXPOSUREAPP-3845) (#1675) * Show aggregated risk result in testmenu riskCalculationFragment * Use extention fun to build string for aggregated result * Show exposure window count and exposure windows as json + fixed a layoutbug with cut off * satisfy lint * Use real values * Connected ExposureResultStore * ENF v2 Calculation adjusted Testmenu Entry for v2 * Adjusted to ExposureResultStore changes * Added logs * Removed and adjusted some old stuff * Made backend parameters a little bit better readable * its better this way * Created additional risk calc info * Refactoring * sourcecheck clean --- .../ui/TestRiskLevelCalculationFragment.kt | 65 +----- ...iskLevelCalculationFragmentCWAViewModel.kt | 214 +++++++----------- .../fragment_test_risk_level_calculation.xml | 60 +---- .../storage/RiskLevelRepository.kt | 2 +- 4 files changed, 112 insertions(+), 229 deletions(-) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt index 9c8a948bbb5..b0e40407ce9 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt @@ -1,18 +1,13 @@ package de.rki.coronawarnapp.test.risklevel.ui -import android.content.Intent import android.os.Bundle import android.view.View import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.navArgs -import com.google.zxing.integration.android.IntentIntegrator -import com.google.zxing.integration.android.IntentResult import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentTestRiskLevelCalculationBinding -import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange -import de.rki.coronawarnapp.sharing.ExposureSharingService import de.rki.coronawarnapp.test.menu.ui.TestMenuItem import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel import de.rki.coronawarnapp.util.di.AutoInject @@ -20,7 +15,6 @@ import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted -import timber.log.Timber import javax.inject.Inject @Suppress("LongMethod") @@ -55,7 +49,6 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le } binding.buttonRetrieveDiagnosisKeys.setOnClickListener { vm.retrieveDiagnosisKeys() } - binding.buttonProvideKeyViaQr.setOnClickListener { vm.scanLocalQRCodeAndProvide() } binding.buttonCalculateRiskLevel.setOnClickListener { vm.calculateRiskLevel() } binding.buttonClearDiagnosisKeyCache.setOnClickListener { vm.clearKeyCache() } @@ -67,66 +60,32 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le ).show() } - vm.riskScoreState.observe2(this) { state -> - binding.labelRiskScore.text = state.riskScoreMsg - binding.labelBackendParameters.text = state.backendParameters - binding.labelExposureSummary.text = state.exposureSummary - binding.labelFormula.text = state.formula - binding.labelExposureInfo.text = state.exposureInfo + vm.additionalRiskCalcInfo.observe2(this) { + binding.labelRiskAdditionalInfo.text = it } - vm.startENFObserver() - vm.apiKeysProvidedEvent.observe2(this) { event -> - Toast.makeText( - requireContext(), - "Provided ${event.keyCount} keys to Google API", - Toast.LENGTH_SHORT - ).show() + vm.aggregatedRiskResult.observe2(this) { + binding.labelAggregatedRiskResult.text = it } - vm.startLocalQRCodeScanEvent.observe2(this) { - IntentIntegrator.forSupportFragment(this) - .setOrientationLocked(false) - .setBeepEnabled(false) - .initiateScan() + vm.exposureWindowCountString.observe2(this) { + binding.labelExposureWindowCount.text = it } - } - - override fun onResume() { - super.onResume() - vm.calculateRiskLevel() - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - val result: IntentResult = - IntentIntegrator.parseActivityResult(requestCode, resultCode, data) - ?: return super.onActivityResult(requestCode, resultCode, data) - if (result.contents == null) { - Toast.makeText(requireContext(), "Cancelled", Toast.LENGTH_LONG).show() - return + vm.exposureWindows.observe2(this) { + binding.labelExposureWindows.text = it } - ExposureSharingService.getOthersKeys(result.contents) { key: AppleLegacyKeyExchange.Key? -> - Timber.i("Keys scanned: %s", key) - if (key == null) { - Toast.makeText( - requireContext(), "No Key data found in QR code", Toast.LENGTH_SHORT - ).show() - return@getOthersKeys Unit - } - - val text = binding.transmissionNumber.text.toString() - val number = if (!text.isBlank()) Integer.valueOf(text) else 5 - vm.provideDiagnosisKey(number, key) + vm.backendParameters.observe2(this) { + binding.labelBackendParameters.text = it } } companion object { val TAG: String = TestRiskLevelCalculationFragment::class.simpleName!! val MENU_ITEM = TestMenuItem( - title = "Risklevel Calculation", - description = "Risklevel calculation related test options.", + title = "ENF v2 Calculation", + description = "Window Mode related overview.", targetId = R.id.test_risklevel_calculation_fragment ) } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index 67d68216e20..24e49d8754a 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -1,24 +1,23 @@ package de.rki.coronawarnapp.test.risklevel.ui import android.content.Context -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData +import com.google.gson.Gson import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.risk.ExposureResult import de.rki.coronawarnapp.risk.ExposureResultStore import de.rki.coronawarnapp.risk.RiskLevel import de.rki.coronawarnapp.risk.RiskLevelTask -import de.rki.coronawarnapp.risk.RiskLevels import de.rki.coronawarnapp.risk.TimeVariables -import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange +import de.rki.coronawarnapp.risk.result.AggregatedRiskResult import de.rki.coronawarnapp.storage.AppDatabase import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.RiskLevelRepository @@ -27,11 +26,11 @@ import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.task.common.DefaultTaskRequest import de.rki.coronawarnapp.task.submitBlocking import de.rki.coronawarnapp.ui.tracing.card.TracingCardStateProvider -import de.rki.coronawarnapp.util.KeyFileHelper import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.security.SecurityHelper +import de.rki.coronawarnapp.util.serialization.BaseGson import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory @@ -39,9 +38,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.sample import kotlinx.coroutines.withContext +import org.joda.time.Instant import timber.log.Timber -import java.io.File -import java.util.UUID import java.util.concurrent.TimeUnit class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( @@ -49,21 +47,25 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( @Assisted private val exampleArg: String?, @AppContext private val context: Context, // App context dispatcherProvider: DispatcherProvider, - private val enfClient: ENFClient, - private val riskLevels: RiskLevels, private val taskController: TaskController, private val keyCacheRepository: KeyCacheRepository, private val appConfigProvider: AppConfigProvider, tracingCardStateProvider: TracingCardStateProvider, + @BaseGson private val gson: Gson, private val exposureResultStore: ExposureResultStore ) : CWAViewModel( dispatcherProvider = dispatcherProvider ) { + init { + Timber.d("CWAViewModel: %s", this) + Timber.d("SavedStateHandle: %s", handle) + Timber.d("Example arg: %s", exampleArg) + } + val startLocalQRCodeScanEvent = SingleLiveEvent() val riskLevelResetEvent = SingleLiveEvent() - val apiKeysProvidedEvent = SingleLiveEvent() - val riskScoreState = MutableLiveData(RiskScoreState()) + val showRiskStatusCard = SubmissionRepository.deviceUIStateFlow.map { it.withSuccess(false) { true } }.asLiveData(dispatcherProvider.Default) @@ -72,13 +74,79 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( .sample(150L) .asLiveData(dispatcherProvider.Default) - init { - Timber.d("CWAViewModel: %s", this) - Timber.d("SavedStateHandle: %s", handle) - Timber.d("Example arg: %s", exampleArg) - } + val exposureWindowCountString = exposureResultStore + .entities + .map { "Retrieved ${it.exposureWindows.size} Exposure Windows" } + .asLiveData() + + val exposureWindows = exposureResultStore + .entities + .map { if (it.exposureWindows.isEmpty()) "Exposure windows list is empty" else gson.toJson(it.exposureWindows) } + .asLiveData() + + val aggregatedRiskResult = exposureResultStore + .entities + .map { if (it.aggregatedRiskResult != null) it.aggregatedRiskResult.toReadableString() else "Aggregated risk result is not available" } + .asLiveData() + + private fun AggregatedRiskResult.toReadableString(): String = StringBuilder() + .appendLine("Total RiskLevel: $totalRiskLevel") + .appendLine("Total Minimum Distinct Encounters With High Risk: $totalMinimumDistinctEncountersWithHighRisk") + .appendLine("Total Minimum Distinct Encounters With Low Risk: $totalMinimumDistinctEncountersWithLowRisk") + .appendLine("Most Recent Date With High Risk: $mostRecentDateWithHighRisk") + .appendLine("Most Recent Date With Low Risk: $mostRecentDateWithLowRisk") + .appendLine("Number of Days With High Risk: $numberOfDaysWithHighRisk") + .appendLine("Number of Days With Low Risk: $numberOfDaysWithLowRisk") + .toString() + + val backendParameters = appConfigProvider + .currentConfig + .map { it.toReadableString() } + .asLiveData() + + private fun ConfigData.toReadableString(): String = StringBuilder() + .appendLine("Transmission RiskLevel Multiplier: $transmissionRiskLevelMultiplier") + .appendLine() + .appendLine("Minutes At Attenuation Filters:") + .appendLine(minutesAtAttenuationFilters) + .appendLine() + .appendLine("Minutes At Attenuation Weights:") + .appendLine(minutesAtAttenuationWeights) + .appendLine() + .appendLine("Transmission RiskLevel Encoding:") + .appendLine(transmissionRiskLevelEncoding) + .appendLine() + .appendLine("Transmission RiskLevel Filters:") + .appendLine(transmissionRiskLevelFilters) + .appendLine() + .appendLine("Normalized Time Per Exposure Window To RiskLevel Mapping:") + .appendLine(normalizedTimePerExposureWindowToRiskLevelMapping) + .appendLine() + .appendLine("Normalized Time Per Day To RiskLevel Mapping List:") + .appendLine(normalizedTimePerDayToRiskLevelMappingList) + .toString() + + // Only update when risk level gets updated + val additionalRiskCalcInfo = RiskLevelRepository + .riskLevelScore + .map { createAdditionalRiskCalcInfo(it) } + .asLiveData() + + private suspend fun createAdditionalRiskCalcInfo(riskLevelScore: Int): String = StringBuilder() + .appendLine("Risk Level: ${RiskLevel.forValue(riskLevelScore)}") + .appendLine("Last successful Risk Level: ${RiskLevelRepository.getLastSuccessfullyCalculatedScore()}") + .appendLine("Last Time Server Fetch: ${LocalData.lastTimeDiagnosisKeysFromServerFetch()}") + .appendLine("Tracing Duration: ${TimeUnit.MILLISECONDS.toDays(TimeVariables.getTimeActiveTracingDuration())} days") + .appendLine("Tracing Duration in last 14 days: ${TimeVariables.getActiveTracingDaysInRetentionPeriod()} days") + .appendLine( + "Last time risk level calculation ${ + LocalData.lastTimeRiskLevelCalculation()?.let { Instant.ofEpochMilli(it) } + }" + ) + .toString() fun retrieveDiagnosisKeys() { + Timber.d("Starting download diagnosis keys task") launch { taskController.submitBlocking( DefaultTaskRequest( @@ -87,11 +155,11 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( originTag = "TestRiskLevelCalculationFragmentCWAViewModel.retrieveDiagnosisKeys()" ) ) - calculateRiskLevel() } } fun calculateRiskLevel() { + Timber.d("Starting calculate risk task") taskController.submit( DefaultTaskRequest( RiskLevelTask::class, @@ -101,6 +169,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( } fun resetRiskLevel() { + Timber.d("Resetting risk level") launch { withContext(Dispatchers.IO) { try { @@ -125,117 +194,8 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( } } - data class RiskScoreState( - val riskScoreMsg: String = "", - val backendParameters: String = "", - val exposureSummary: String = "", - val formula: String = "", - val exposureInfo: String = "" - ) - - fun startENFObserver() { - launch { - try { - val appConfig = appConfigProvider.getAppConfig() - - var workState = riskScoreState.value!! - - val exposureWindows = enfClient.exposureWindows() - - val riskResultsPerWindow = - exposureWindows.mapNotNull { window -> - riskLevels.calculateRisk(appConfig, window)?.let { window to it } - }.toMap() - - val aggregatedResult = riskLevels.aggregateResults(appConfig, riskResultsPerWindow) - - val riskAsString = "Level: ${RiskLevelRepository.getLastCalculatedScore()}\n" + - "Last successful Level: " + - "${LocalData.lastSuccessfullyCalculatedRiskLevel()}\n" + - "Calculated Score: ${aggregatedResult}\n" + - "Last Time Server Fetch: ${LocalData.lastTimeDiagnosisKeysFromServerFetch()}\n" + - "Tracing Duration: " + - "${TimeUnit.MILLISECONDS.toDays(TimeVariables.getTimeActiveTracingDuration())} days \n" + - "Tracing Duration in last 14 days: " + - "${TimeVariables.getActiveTracingDaysInRetentionPeriod()} days \n" + - "Last time risk level calculation ${LocalData.lastTimeRiskLevelCalculation()}" - - workState = workState.copy(riskScoreMsg = riskAsString) - - val configAsString = - "Transmission RiskLevel Multiplier: ${appConfig.transmissionRiskLevelMultiplier}\n" + - "Minutes At Attenuation Filters: ${appConfig.minutesAtAttenuationFilters}\n" + - "Minutes At Attenuation Weights: ${appConfig.minutesAtAttenuationWeights}" + - "Transmission RiskLevel Encoding: ${appConfig.transmissionRiskLevelEncoding}" + - "Transmission RiskLevel Filters: ${appConfig.transmissionRiskLevelFilters}" + - "Normalized Time Per Exposure Window To RiskLevel Mapping: ${appConfig.normalizedTimePerExposureWindowToRiskLevelMapping}" + - "Normalized Time Per Day To RiskLevel Mapping List: ${appConfig.normalizedTimePerDayToRiskLevelMappingList}" - workState = workState.copy(backendParameters = configAsString) - - val summaryAsString = - "Total RiskLevel: ${aggregatedResult.totalRiskLevel}" + - "Total Minimum Distinct Encounters With High Risk: ${aggregatedResult.totalMinimumDistinctEncountersWithHighRisk}" + - "Total Minimum Distinct Encounters With Low Risk: ${aggregatedResult.totalMinimumDistinctEncountersWithLowRisk}" + - "Most Recent Date With High Risk: ${aggregatedResult.mostRecentDateWithHighRisk}" + - "Most Recent Date With Low Risk: ${aggregatedResult.mostRecentDateWithLowRisk}" - - workState = workState.copy(exposureSummary = summaryAsString) - - riskScoreState.postValue(workState) - } catch (e: Exception) { - e.report(ExceptionCategory.EXPOSURENOTIFICATION) - } - } - } - - data class DiagnosisKeyProvidedEvent( - val keyCount: Int - ) - - fun provideDiagnosisKey(transmissionNumber: Int, key: AppleLegacyKeyExchange.Key) { - val appleKeyList = mutableListOf() - - AppleLegacyKeyExchange.Key.newBuilder() - .setKeyData(key.keyData) - .setRollingPeriod(144) - .setRollingStartNumber(key.rollingStartNumber) - .setTransmissionRiskLevel(transmissionNumber) - .build() - .also { appleKeyList.add(it) } - - val appleFiles = listOf( - AppleLegacyKeyExchange.File.newBuilder() - .addAllKeys(appleKeyList) - .build() - ) - - val dir = File(File(context.getExternalFilesDir(null), "key-export"), UUID.randomUUID().toString()) - dir.mkdirs() - - var googleFileList: List - launch { - googleFileList = KeyFileHelper.asyncCreateExportFiles(appleFiles, dir) - - Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys") - try { - // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API - enfClient.provideDiagnosisKeys(googleFileList) - apiKeysProvidedEvent.postValue( - DiagnosisKeyProvidedEvent( - keyCount = appleFiles.size - ) - ) - } catch (e: Exception) { - e.report(ExceptionCategory.EXPOSURENOTIFICATION) - } - } - } - - fun scanLocalQRCodeAndProvide() { - startLocalQRCodeScanEvent.postValue(Unit) - } - fun clearKeyCache() { + Timber.d("Clearing key cache") launch { keyCacheRepository.clear() } } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml index a58a1ff4308..7d94e7c3dca 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml @@ -32,7 +32,6 @@ - - - - - - - -