From 8367503993ad22153d6dc39f8bdc71c36511c3d6 Mon Sep 17 00:00:00 2001 From: Matthias Urhahn Date: Fri, 29 Jan 2021 10:30:37 +0100 Subject: [PATCH 01/14] Skeleton classes for privacy preserving analytics (EXPOSUREAPP-4753) (#2225) * Skeleton classes for Analytics. * Rebased branch. * Adjust interface names. * Adjust TODOs, to calm the LINTER. WIP --- .../appconfig/AnalyticsConfig.kt | 13 +++++++ .../appconfig/AppConfigModule.kt | 5 +++ .../mapping/AnalyticsConfigMapper.kt | 23 +++++++++++++ .../appconfig/mapping/ConfigMapping.kt | 3 ++ .../appconfig/mapping/ConfigParser.kt | 7 ++-- .../appconfig/mapping/DefaultConfigMapping.kt | 4 ++- .../datadonation/analytics/Analytics.kt | 34 +++++++++++++++++++ .../analytics/AnalyticsException.kt | 6 ++++ .../datadonation/analytics/AnalyticsModule.kt | 30 ++++++++++++++++ .../analytics/AnalyticsSettings.kt | 7 ++++ .../analytics/modules/DonorModule.kt | 32 +++++++++++++++++ .../NewExposureWindowsDonor.kt | 31 +++++++++++++++++ .../keysubmission/KeySubmissionStateDonor.kt | 22 ++++++++++++ .../registeredtest/RegisteredTestDonor.kt | 22 ++++++++++++ .../RiskLevelHistoryChangesDonor.kt | 22 ++++++++++++ .../appconfig/mapping/ConfigParserTest.kt | 6 +++- 16 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsException.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsModule.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsSettings.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/DonorModule.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/NewExposureWindowsDonor.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/KeySubmissionStateDonor.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/RegisteredTestDonor.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/risklevelhistory/RiskLevelHistoryChangesDonor.kt diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt new file mode 100644 index 00000000000..00d22514bf6 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt @@ -0,0 +1,13 @@ +package de.rki.coronawarnapp.appconfig + +import de.rki.coronawarnapp.appconfig.mapping.ConfigMapper + +interface AnalyticsConfig { + + val probabilityToSubmit: Float + val probabilityToSubmitAfterRiskCalculation: Float + val probabilityToSubmitNewExposureWindows: Float + val safetyNetRequirements: SafetyNetRequirements + + interface Mapper : ConfigMapper +} 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 9c23d3ea7c8..75fe2040043 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,6 +3,7 @@ package de.rki.coronawarnapp.appconfig import dagger.Module import dagger.Provides import de.rki.coronawarnapp.appconfig.download.AppConfigApiV2 +import de.rki.coronawarnapp.appconfig.mapping.AnalyticsConfigMapper import de.rki.coronawarnapp.appconfig.mapping.CWAConfigMapper import de.rki.coronawarnapp.appconfig.mapping.ExposureDetectionConfigMapper import de.rki.coronawarnapp.appconfig.mapping.ExposureWindowRiskCalculationConfigMapper @@ -68,6 +69,10 @@ class AppConfigModule { fun surveyMapper(mapper: SurveyConfigMapper): SurveyConfig.Mapper = mapper + @Provides + fun analyticsMapper(mapper: AnalyticsConfigMapper): + AnalyticsConfig.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/mapping/AnalyticsConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt new file mode 100644 index 00000000000..cf3157b4375 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt @@ -0,0 +1,23 @@ +package de.rki.coronawarnapp.appconfig.mapping + +import dagger.Reusable +import de.rki.coronawarnapp.appconfig.AnalyticsConfig +import de.rki.coronawarnapp.appconfig.SafetyNetRequirements +import de.rki.coronawarnapp.appconfig.SafetyNetRequirementsContainer +import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import javax.inject.Inject + +@Reusable +class AnalyticsConfigMapper @Inject constructor() : AnalyticsConfig.Mapper { + override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): AnalyticsConfig { + // TODO + return AnalyticsConfigContainer() + } + + data class AnalyticsConfigContainer( + override val safetyNetRequirements: SafetyNetRequirements = SafetyNetRequirementsContainer(), + override val probabilityToSubmit: Float = 1f, + override val probabilityToSubmitAfterRiskCalculation: Float = 1f, + override val probabilityToSubmitNewExposureWindows: Float = 1f + ) : AnalyticsConfig +} 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 c04815b68ee..a55c67fc24a 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 @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.appconfig.mapping +import de.rki.coronawarnapp.appconfig.AnalyticsConfig import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig @@ -17,4 +18,6 @@ interface ConfigMapping : val rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid val survey: SurveyConfig + + val analytics: AnalyticsConfig } 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 ebe1f67b98f..6c50b4cb724 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 @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.appconfig.mapping import dagger.Reusable +import de.rki.coronawarnapp.appconfig.AnalyticsConfig import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig @@ -16,7 +17,8 @@ class ConfigParser @Inject constructor( private val keyDownloadConfigMapper: KeyDownloadConfig.Mapper, private val exposureDetectionConfigMapper: ExposureDetectionConfig.Mapper, private val exposureWindowRiskCalculationConfigMapper: ExposureWindowRiskCalculationConfig.Mapper, - private val surveyConfigMapper: SurveyConfig.Mapper + private val surveyConfigMapper: SurveyConfig.Mapper, + private val analyticsConfigMapper: AnalyticsConfig.Mapper ) { fun parse(configBytes: ByteArray): ConfigMapping = try { @@ -27,7 +29,8 @@ class ConfigParser @Inject constructor( keyDownloadConfig = keyDownloadConfigMapper.map(it), exposureDetectionConfig = exposureDetectionConfigMapper.map(it), exposureWindowRiskCalculationConfig = exposureWindowRiskCalculationConfigMapper.map(it), - survey = surveyConfigMapper.map(it) + survey = surveyConfigMapper.map(it), + analytics = analyticsConfigMapper.map(it) ) } } 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 9c3062f7374..f7063cbff7f 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 @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.appconfig.mapping +import de.rki.coronawarnapp.appconfig.AnalyticsConfig import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig @@ -13,7 +14,8 @@ data class DefaultConfigMapping( val keyDownloadConfig: KeyDownloadConfig, val exposureDetectionConfig: ExposureDetectionConfig, val exposureWindowRiskCalculationConfig: ExposureWindowRiskCalculationConfig, - override val survey: SurveyConfig + override val survey: SurveyConfig, + override val analytics: AnalyticsConfig ) : ConfigMapping, CWAConfig by cwaConfig, KeyDownloadConfig by keyDownloadConfig, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt new file mode 100644 index 00000000000..0c20d712edd --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt @@ -0,0 +1,34 @@ +package de.rki.coronawarnapp.datadonation.analytics + +import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule +import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class Analytics @Inject constructor( + private val appConfigProvider: AppConfigProvider, + private val deviceAttestation: DeviceAttestation, + private val donorModules: Set, + private val settings: AnalyticsSettings +) { + + val isEnabled: Boolean = true + + suspend fun submitAnalyticsData() { +// val request: DonorModule.Request = ... +// // Collect data from all donor modules +// val contributions = donorModules.map { it.beginDonation(request) } +// val ppaContainer: Any = ... +// +// contributions.forEach { +// it.injectData(ppaContainer) +// } +// +// val success = trySubmission(ppaContainer) +// contributions.forEach { +// it.finishDonation(success) +// } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsException.kt new file mode 100644 index 00000000000..ffadf9cc40d --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsException.kt @@ -0,0 +1,6 @@ +package de.rki.coronawarnapp.datadonation.analytics + +class AnalyticsException( + message: String?, + cause: Throwable? +) : Exception(message, cause) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsModule.kt new file mode 100644 index 00000000000..a716c9af371 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsModule.kt @@ -0,0 +1,30 @@ +package de.rki.coronawarnapp.datadonation.analytics + +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoSet +import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule +import de.rki.coronawarnapp.datadonation.analytics.modules.exposurewindows.NewExposureWindowsDonor +import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.KeySubmissionStateDonor +import de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest.RegisteredTestDonor +import de.rki.coronawarnapp.datadonation.analytics.modules.risklevelhistory.RiskLevelHistoryChangesDonor + +@Module +class AnalyticsModule { + + @IntoSet + @Provides + fun newExposureWindows(module: NewExposureWindowsDonor): DonorModule = module + + @IntoSet + @Provides + fun keySubmission(module: KeySubmissionStateDonor): DonorModule = module + + @IntoSet + @Provides + fun registeredTest(module: RegisteredTestDonor): DonorModule = module + + @IntoSet + @Provides + fun riskLevelHistory(module: RiskLevelHistoryChangesDonor): DonorModule = module +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsSettings.kt new file mode 100644 index 00000000000..3f8eaeb25fa --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsSettings.kt @@ -0,0 +1,7 @@ +package de.rki.coronawarnapp.datadonation.analytics + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AnalyticsSettings @Inject constructor() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/DonorModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/DonorModule.kt new file mode 100644 index 00000000000..9cc7b5b1031 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/DonorModule.kt @@ -0,0 +1,32 @@ +package de.rki.coronawarnapp.datadonation.analytics.modules + +interface DonorModule { + + suspend fun beginDonation(request: Request): Contribution + + /** + * Data that the modules may need to fullfil the request + */ + interface Request + + /** + * An object that adds the data to the protobuf container, such that the Analytics class doesn't need to know the + * individual data types. + * This also acts as a callback so the donor modules know when to discard data. + */ + interface Contribution { + // TODO replace with protobuf `PPAData` class + /** + * You will be passed a protobuf container where the module will add it's data + */ + suspend fun injectData(protobufContainer: Any) + + /** + * This will be called with the submission result. + * For modules that track a data delta between each submission time: + * If the submission failed, i.e. network issues, the donor module may collect their data for the next attempt. + * If the submission was sucessful, the module may discard the data as it was submitted. + */ + suspend fun finishDonation(successful: Boolean) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/NewExposureWindowsDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/NewExposureWindowsDonor.kt new file mode 100644 index 00000000000..1846f505066 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/NewExposureWindowsDonor.kt @@ -0,0 +1,31 @@ +package de.rki.coronawarnapp.datadonation.analytics.modules.exposurewindows + +import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NewExposureWindowsDonor @Inject constructor() : DonorModule { + + override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution { + return CollectedData( + protobuf = Any(), + onContributionFinished = { success -> + // TODO + } + ) + } + + data class CollectedData( + val protobuf: Any, + val onContributionFinished: suspend (Boolean) -> Unit + ) : DonorModule.Contribution { + override suspend fun injectData(protobufContainer: Any) { + // TODO "Add this specific protobuf to the top level protobuf container" + } + + override suspend fun finishDonation(successful: Boolean) { + onContributionFinished(successful) + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/KeySubmissionStateDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/KeySubmissionStateDonor.kt new file mode 100644 index 00000000000..5bb6fab83f8 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/KeySubmissionStateDonor.kt @@ -0,0 +1,22 @@ +package de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission + +import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class KeySubmissionStateDonor @Inject constructor() : DonorModule { + + override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution { + // TODO + return object : DonorModule.Contribution { + override suspend fun injectData(protobufContainer: Any) { + // TODO + } + + override suspend fun finishDonation(successful: Boolean) { + // TODO + } + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/RegisteredTestDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/RegisteredTestDonor.kt new file mode 100644 index 00000000000..56d57fb6fe8 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/registeredtest/RegisteredTestDonor.kt @@ -0,0 +1,22 @@ +package de.rki.coronawarnapp.datadonation.analytics.modules.registeredtest + +import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RegisteredTestDonor @Inject constructor() : DonorModule { + + override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution { + // TODO + return object : DonorModule.Contribution { + override suspend fun injectData(protobufContainer: Any) { + // TODO + } + + override suspend fun finishDonation(successful: Boolean) { + // TODO + } + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/risklevelhistory/RiskLevelHistoryChangesDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/risklevelhistory/RiskLevelHistoryChangesDonor.kt new file mode 100644 index 00000000000..da9db75e515 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/risklevelhistory/RiskLevelHistoryChangesDonor.kt @@ -0,0 +1,22 @@ +package de.rki.coronawarnapp.datadonation.analytics.modules.risklevelhistory + +import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RiskLevelHistoryChangesDonor @Inject constructor() : DonorModule { + + override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution { + // TODO + return object : DonorModule.Contribution { + override suspend fun injectData(protobufContainer: Any) { + // TODO + } + + override suspend fun finishDonation(successful: Boolean) { + // TODO + } + } + } +} 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 a98862b7a81..87713174b43 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 @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.appconfig.mapping import com.google.protobuf.InvalidProtocolBufferException +import de.rki.coronawarnapp.appconfig.AnalyticsConfig import de.rki.coronawarnapp.appconfig.CWAConfig import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig @@ -28,6 +29,7 @@ class ConfigParserTest : BaseTest() { @MockK lateinit var exposureDetectionConfigMapper: ExposureDetectionConfig.Mapper @MockK lateinit var exposureWindowRiskCalculationConfigMapper: ExposureWindowRiskCalculationConfig.Mapper @MockK lateinit var surveyConfigMapper: SurveyConfig.Mapper + @MockK lateinit var analyticsConfigMapper: AnalyticsConfig.Mapper private val appConfig171 = File("src/test/resources/appconfig_1_7_1.bin") private val appConfig180 = File("src/test/resources/appconfig_1_8_0.bin") @@ -41,6 +43,7 @@ class ConfigParserTest : BaseTest() { every { exposureDetectionConfigMapper.map(any()) } returns mockk() every { exposureWindowRiskCalculationConfigMapper.map(any()) } returns mockk() every { surveyConfigMapper.map(any()) } returns mockk() + every { analyticsConfigMapper.map(any()) } returns mockk() appConfig171.exists() shouldBe true appConfig180.exists() shouldBe true @@ -56,7 +59,8 @@ class ConfigParserTest : BaseTest() { keyDownloadConfigMapper = keyDownloadConfigMapper, exposureDetectionConfigMapper = exposureDetectionConfigMapper, exposureWindowRiskCalculationConfigMapper = exposureWindowRiskCalculationConfigMapper, - surveyConfigMapper = surveyConfigMapper + surveyConfigMapper = surveyConfigMapper, + analyticsConfigMapper = analyticsConfigMapper ) @Test From e9391c015435922d8e4949d7aedeb5b8ed974e51 Mon Sep 17 00:00:00 2001 From: Matthias Urhahn Date: Wed, 3 Feb 2021 12:52:38 +0100 Subject: [PATCH 02/14] PPA UserInput Selection Screens (EXPOSUREAPP-4752) (#2270) * PPA User Input Strings * Data Donation: PPA User Input. First draft. TODO: Districts Tests * Fix interaction with radio button.. Fix state matching for NRW Add district mapping and display. Fix "unspecified" element position. * Test skeletons. * Finish unit tests. * Wish the LINTer a nice evening. * Move extension functions into their own file and make them public to allow label-related code to be reused on the settings screen. Additional unit tests that check the federal state short code mapping. * XML formatting. * Address additional PR comments. * Adjust radiobutton accent color. --- .../test/menu/ui/TestMenuFragmentViewModel.kt | 4 +- .../test/playground/ui/PlaygroundFragment.kt | 60 + .../test/playground/ui/PlaygroundModule.kt | 16 + .../test/playground/ui/PlaygroundViewModel.kt | 12 + .../ui/main/MainActivityTestModule.kt | 5 + .../res/layout/fragment_test_playground.xml | 30 + .../res/navigation/test_nav_graph.xml | 8 + .../ppdd-ppa-administrative-unit-set.json | 3298 +++++++++++++++++ .../analytics/AnalyticsSettings.kt | 51 +- .../analytics/common/Districts.kt | 42 + .../analytics/common/PpaDataExtensions.kt | 66 + .../analytics/ui/AnalyticsUIModule.kt | 25 + .../ui/input/AnalyticsUserInputFragment.kt | 64 + .../ui/input/AnalyticsUserInputViewModel.kt | 130 + .../analytics/ui/input/UserInfoItem.kt | 9 + .../analytics/ui/input/UserInfoItemAdapter.kt | 44 + .../ui/main/MainActivityModule.kt | 4 +- .../main/res/color/radiobutton_accented.xml | 5 + ...lytics_ppa_userinfo_input_adapter_item.xml | 31 + .../analytics_ppa_userinfo_input_fragment.xml | 26 + .../src/main/res/navigation/nav_graph.xml | 10 +- .../src/main/res/values-de/strings.xml | 56 + .../src/main/res/values/strings.xml | 56 + .../analytics/common/DistrictsTest.kt | 72 + .../analytics/common/PpaDataExtensionsTest.kt | 29 + .../analytics/ui/AnalyticsSettingsTest.kt | 79 + .../input/AnalyticsUserInputViewModelTest.kt | 180 + 27 files changed, 4408 insertions(+), 4 deletions(-) create mode 100644 Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundFragment.kt create mode 100644 Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundModule.kt create mode 100644 Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundViewModel.kt create mode 100644 Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_playground.xml create mode 100644 Corona-Warn-App/src/main/assets/ppdd-ppa-administrative-unit-set.json create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Districts.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/ui/AnalyticsUIModule.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/ui/input/AnalyticsUserInputFragment.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/ui/input/AnalyticsUserInputViewModel.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/ui/input/UserInfoItem.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/ui/input/UserInfoItemAdapter.kt create mode 100644 Corona-Warn-App/src/main/res/color/radiobutton_accented.xml create mode 100644 Corona-Warn-App/src/main/res/layout/analytics_ppa_userinfo_input_adapter_item.xml create mode 100644 Corona-Warn-App/src/main/res/layout/analytics_ppa_userinfo_input_fragment.xml create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/DistrictsTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/ui/AnalyticsSettingsTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/ui/input/AnalyticsUserInputViewModelTest.kt diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt index a28ebf70941..992dc606712 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt @@ -9,6 +9,7 @@ import de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragment import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment +import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment import de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragment import de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragment @@ -28,7 +29,8 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() { SubmissionTestFragment.MENU_ITEM, SettingsCrashReportFragment.MENU_ITEM, MiscInfoFragment.MENU_ITEM, - ContactDiaryTestFragment.MENU_ITEM + ContactDiaryTestFragment.MENU_ITEM, + PlaygroundFragment.MENU_ITEM ).let { MutableLiveData(it) } } val showTestScreenEvent = SingleLiveEvent() diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundFragment.kt new file mode 100644 index 00000000000..82abd140ca3 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundFragment.kt @@ -0,0 +1,60 @@ +package de.rki.coronawarnapp.test.playground.ui + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentTestPlaygroundBinding +import de.rki.coronawarnapp.datadonation.analytics.ui.input.AnalyticsUserInputFragment +import de.rki.coronawarnapp.datadonation.analytics.ui.input.AnalyticsUserInputFragmentArgs +import de.rki.coronawarnapp.test.menu.ui.TestMenuItem +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import javax.inject.Inject + +@SuppressLint("SetTextI18n") +class PlaygroundFragment : Fragment(R.layout.fragment_test_playground), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + + private val vm: PlaygroundViewModel by cwaViewModels { viewModelFactory } + private val binding: FragmentTestPlaygroundBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.apply { + dataDonationUserinfoAgegroup.setOnClickListener { + findNavController().navigate( + R.id.analyticsUserInputFragment, + AnalyticsUserInputFragmentArgs(AnalyticsUserInputFragment.InputType.AGE_GROUP).toBundle() + ) + } + dataDonationUserinfoFederalstate.setOnClickListener { + findNavController().navigate( + R.id.analyticsUserInputFragment, + AnalyticsUserInputFragmentArgs(AnalyticsUserInputFragment.InputType.FEDERAL_STATE).toBundle() + ) + } + dataDonationUserinfoDistrict.setOnClickListener { + findNavController().navigate( + R.id.analyticsUserInputFragment, + AnalyticsUserInputFragmentArgs(AnalyticsUserInputFragment.InputType.DISTRICT).toBundle() + ) + } + } + } + + companion object { + val TAG: String = PlaygroundFragment::class.simpleName!! + val MENU_ITEM = TestMenuItem( + title = "Playground", + description = "Random options for not integrated features", + targetId = R.id.playgroundFragment + ) + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundModule.kt new file mode 100644 index 00000000000..85f6e94a93d --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundModule.kt @@ -0,0 +1,16 @@ +package de.rki.coronawarnapp.test.playground.ui + +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey + +@Module +abstract class PlaygroundModule { + @Binds + @IntoMap + @CWAViewModelKey(PlaygroundViewModel::class) + abstract fun playground(factory: PlaygroundViewModel.Factory): CWAViewModelFactory +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundViewModel.kt new file mode 100644 index 00000000000..d411ea539ab --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/playground/ui/PlaygroundViewModel.kt @@ -0,0 +1,12 @@ +package de.rki.coronawarnapp.test.playground.ui + +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory + +class PlaygroundViewModel @AssistedInject constructor() : CWAViewModel() { + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt index 43dba449de0..889dba9383e 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt @@ -14,6 +14,8 @@ import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragmentModule import de.rki.coronawarnapp.test.menu.ui.TestMenuFragment import de.rki.coronawarnapp.test.menu.ui.TestMenuFragmentModule +import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment +import de.rki.coronawarnapp.test.playground.ui.PlaygroundModule import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragmentModule import de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragment @@ -50,4 +52,7 @@ abstract class MainActivityTestModule { @ContributesAndroidInjector(modules = [ContactDiaryTestFragmentModule::class]) abstract fun contactDiaryTest(): ContactDiaryTestFragment + + @ContributesAndroidInjector(modules = [PlaygroundModule::class]) + abstract fun playground(): PlaygroundFragment } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_playground.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_playground.xml new file mode 100644 index 00000000000..5e7ffa8ec0d --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_playground.xml @@ -0,0 +1,30 @@ + + + + + +