Skip to content
Snippets Groups Projects
Unverified Commit 5da95c31 authored by Matthias Urhahn's avatar Matthias Urhahn Committed by GitHub
Browse files

Merge pull request #1677 from corona-warn-app/feature/3456-enfv2-main-branch

Migration to google ENF client v2 (window mode) (EXPOSUREAPP-3456)
parents e1ab02f1 fa8315d6
No related branches found
No related tags found
No related merge requests found
Showing
with 265 additions and 615 deletions
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<Context>()
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()
}
}
......@@ -16,7 +16,6 @@ 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.TemporaryExposureKey
import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson
......@@ -32,25 +31,24 @@ 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.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
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
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
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
......@@ -66,6 +64,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 {
......@@ -85,10 +85,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
}
}
private val enfClient by lazy {
AppInjector.component.enfClient
}
private var myExposureKeysJSON: String? = null
private var myExposureKeys: List<TemporaryExposureKey>? = mutableListOf()
private var otherExposureKey: AppleLegacyKeyExchange.Key? = null
......@@ -96,8 +92,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<QRPagerAdapter.QRViewHolder>
......@@ -108,8 +102,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)
......@@ -127,7 +119,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
binding.apply {
buttonApiTestStart.setOnClickListener { start() }
buttonApiGetExposureKeys.setOnClickListener { getExposureKeys() }
buttonApiGetCheckExposure.setOnClickListener { checkExposure() }
buttonApiScanQrCode.setOnClickListener {
IntentIntegrator.forSupportFragment(this@TestForAPIFragment)
......@@ -168,8 +159,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
buttonRetrieveExposureSummary.setOnClickListener {
vm.launch {
val summary = ExposureSummaryRepository.getExposureSummaryRepository()
.getExposureSummaryEntities().toString()
val summary = exposureResultStore.entities.first().exposureWindows.toString()
withContext(Dispatchers.Main) {
showToast(summary)
......@@ -206,12 +196,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
}
}
override fun onResume() {
super.onResume()
updateExposureSummaryDisplay(null)
}
private val prettyKey = { key: AppleLegacyKeyExchange.Key ->
StringBuilder()
.append("\nKey data: ${key.keyData}")
......@@ -274,9 +258,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<AppleLegacyKeyExchange.Key>()
for (key in otherExposureKeyList) {
......@@ -298,7 +279,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,15 +287,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,
AppInjector.component.appConfigProvider.getAppConfig().exposureDetectionConfiguration,
token!!
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)
}
......@@ -322,51 +301,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
}
}
private fun checkExposure() {
Timber.d("Check Exposure with token $token")
lifecycleScope.launch {
try {
val exposureSummary =
InternalExposureNotificationClient.asyncGetExposureSummary(token!!)
updateExposureSummaryDisplay(exposureSummary)
showToast("Updated Exposure Summary with token $token")
Timber.d("Received exposure with token $token from QR Code")
Timber.i(exposureSummary.toString())
} catch (e: Exception) {
e.report(ExceptionCategory.EXPOSURENOTIFICATION)
}
}
}
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 updateKeysDisplay() {
val myKeys =
......
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 with token ${event.token}",
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
)
}
......
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.android.gms.nearby.exposurenotification.ExposureInformation
import com.google.gson.Gson
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.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.nearby.InternalExposureNotificationClient
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,12 +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.di.AppInjector
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
......@@ -40,32 +38,34 @@ 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
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
@Assisted private val handle: SavedStateHandle,
@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,
tracingCardStateProvider: TracingCardStateProvider
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<Unit>()
val riskLevelResetEvent = SingleLiveEvent<Unit>()
val apiKeysProvidedEvent = SingleLiveEvent<DiagnosisKeyProvidedEvent>()
val riskScoreState = MutableLiveData<RiskScoreState>(RiskScoreState())
val showRiskStatusCard = SubmissionRepository.deviceUIStateFlow.map {
it.withSuccess(false) { true }
}.asLiveData(dispatcherProvider.Default)
......@@ -74,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(
......@@ -89,11 +155,11 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
originTag = "TestRiskLevelCalculationFragmentCWAViewModel.retrieveDiagnosisKeys()"
)
)
calculateRiskLevel()
}
}
fun calculateRiskLevel() {
Timber.d("Starting calculate risk task")
taskController.submit(
DefaultTaskRequest(
RiskLevelTask::class,
......@@ -103,6 +169,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
}
fun resetRiskLevel() {
Timber.d("Resetting risk level")
launch {
withContext(Dispatchers.IO) {
try {
......@@ -113,10 +180,11 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
// Export File Reset
keyCacheRepository.clear()
exposureResultStore.entities.value = ExposureResult(emptyList(), null)
LocalData.lastCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw)
LocalData.lastSuccessfullyCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw)
LocalData.lastTimeDiagnosisKeysFromServerFetch(null)
LocalData.googleApiToken(null)
} catch (e: Exception) {
e.report(ExceptionCategory.INTERNAL)
}
......@@ -126,184 +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 {
var workState = riskScoreState.value!!
val googleToken = LocalData.googleApiToken() ?: UUID.randomUUID().toString()
val exposureSummary =
InternalExposureNotificationClient.asyncGetExposureSummary(googleToken)
val expDetectConfig: RiskCalculationConfig =
AppInjector.component.appConfigProvider.getAppConfig()
val riskLevelScore = riskLevels.calculateRiskScore(
expDetectConfig.attenuationDuration,
exposureSummary
)
val riskAsString = "Level: ${RiskLevelRepository.getLastCalculatedScore()}\n" +
"Last successful Level: " +
"${LocalData.lastSuccessfullyCalculatedRiskLevel()}\n" +
"Calculated Score: ${riskLevelScore}\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 lowClass =
expDetectConfig.riskScoreClasses.riskClassesList?.find { low -> low.label == "LOW" }
val highClass =
expDetectConfig.riskScoreClasses.riskClassesList?.find { high -> high.label == "HIGH" }
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}"
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}"
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)
}
}
}
private suspend fun asyncGetExposureInformation(token: String): List<ExposureInformation> =
suspendCoroutine { cont ->
enfClient.internalClient.getExposureInformation(token)
.addOnSuccessListener {
cont.resume(it)
}.addOnFailureListener {
cont.resumeWithException(it)
}
}
data class DiagnosisKeyProvidedEvent(
val keyCount: Int,
val token: String
)
fun provideDiagnosisKey(transmissionNumber: Int, key: AppleLegacyKeyExchange.Key) {
val token = UUID.randomUUID().toString()
LocalData.googleApiToken(token)
val appleKeyList = mutableListOf<AppleLegacyKeyExchange.Key>()
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"), token)
dir.mkdirs()
var googleFileList: List<File>
launch {
googleFileList = KeyFileHelper.asyncCreateExportFiles(appleFiles, dir)
Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys with token $token")
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
)
apiKeysProvidedEvent.postValue(
DiagnosisKeyProvidedEvent(
keyCount = appleFiles.size,
token = token
)
)
} catch (e: Exception) {
e.report(ExceptionCategory.EXPOSURENOTIFICATION)
}
}
}
fun scanLocalQRCodeAndProvide() {
startLocalQRCodeScanEvent.postValue(Unit)
}
fun clearKeyCache() {
Timber.d("Clearing key cache")
launch { keyCacheRepository.clear() }
}
......
......@@ -61,36 +61,6 @@
android:layout_marginBottom="@dimen/spacing_tiny"
android:text="@string/test_api_exposure_summary_headline" />
<TextView
android:id="@+id/label_exposure_summary_matchedKeyCount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/test_api_body_matchedKeyCount" />
<TextView
android:id="@+id/label_exposure_summary_daysSinceLastExposure"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/test_api_body_daysSinceLastExposure" />
<TextView
android:id="@+id/label_exposure_summary_maximumRiskScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/test_api_body_maximumRiskScore" />
<TextView
android:id="@+id/label_exposure_summary_summationRiskScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/test_api_body_summation_risk" />
<TextView
android:id="@+id/label_exposure_summary_attenuation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/test_api_body_attenuation" />
<Button
android:id="@+id/button_api_scan_qr_code"
style="@style/buttonPrimary"
......@@ -106,14 +76,6 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:text="@string/test_api_button_enter_other_keys" />
<Button
android:id="@+id/button_api_get_check_exposure"
style="@style/buttonPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:text="@string/test_api_button_check_exposure" />
</LinearLayout>
<LinearLayout
......
......@@ -32,7 +32,6 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_normal"
android:orientation="vertical">
<TextView
......@@ -60,34 +59,6 @@
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Transmission Risk Level for scan: " />
<EditText
android:id="@+id/transmission_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="number"
android:text="5" />
</LinearLayout>
<Button
android:id="@+id/button_provide_key_via_qr"
style="@style/buttonPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_normal"
android:text="Scan Local QR Code" />
<Button
android:id="@+id/button_retrieve_diagnosis_keys"
style="@style/buttonPrimary"
......@@ -121,30 +92,30 @@
android:text="Clear Diagnosis-Key cache" />
<TextView
android:id="@+id/label_exposure_summary_title"
android:id="@+id/label_aggregated_risk_result_title"
style="@style/headline6"
android:accessibilityHeading="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:text="Exposure Summary" />
android:text="Aggregated Risk Result" />
<TextView
android:id="@+id/label_exposure_summary"
android:id="@+id/label_aggregated_risk_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-" />
<TextView
android:id="@+id/label_risk_score_title"
android:id="@+id/label_risk_additional_info_title"
style="@style/headline6"
android:accessibilityHeading="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Risk Score" />
android:text="Risk Calculation Additional Information" />
<TextView
android:id="@+id/label_risk_score"
android:id="@+id/label_risk_additional_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-" />
......@@ -164,31 +135,24 @@
android:text="-" />
<TextView
android:id="@+id/label_formula_title"
android:id="@+id/label_exposure_window_title"
style="@style/headline6"
android:accessibilityHeading="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Used Formula" />
android:text="Exposure Windows" />
<TextView
android:id="@+id/label_formula"
android:id="@+id/label_exposure_window_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-" />
<TextView
android:id="@+id/label_exposure_info_title"
style="@style/headline6"
android:accessibilityHeading="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Exposure Information" />
<TextView
android:id="@+id/label_exposure_info"
android:layout_width="match_parent"
android:id="@+id/label_exposure_windows"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dp"
android:text="-" />
</LinearLayout>
......
......@@ -3,11 +3,11 @@ package de.rki.coronawarnapp.appconfig
import android.content.Context
import dagger.Module
import dagger.Provides
import de.rki.coronawarnapp.appconfig.download.AppConfigApiV2
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.appconfig.sources.remote.AppConfigApiV1
import de.rki.coronawarnapp.appconfig.sources.remote.AppConfigHttpCache
import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient
import de.rki.coronawarnapp.environment.download.DownloadCDNServerUrl
......@@ -42,7 +42,7 @@ class AppConfigModule {
@DownloadCDNServerUrl url: String,
gsonConverterFactory: GsonConverterFactory,
@AppConfigHttpCache cache: Cache
): AppConfigApiV1 {
): AppConfigApiV2 {
val cachingClient = client.newBuilder().apply {
cache(cache)
......@@ -57,21 +57,23 @@ class AppConfigModule {
.baseUrl(url)
.addConverterFactory(gsonConverterFactory)
.build()
.create(AppConfigApiV1::class.java)
.create(AppConfigApiV2::class.java)
}
@Provides
fun cwaMapper(mapper: CWAConfigMapper): CWAConfig.Mapper = mapper
fun cwaMapper(mapper: CWAConfigMapper):
CWAConfig.Mapper = mapper
@Provides
fun downloadMapper(mapper: KeyDownloadParametersMapper): KeyDownloadConfig.Mapper = mapper
@Provides
fun exposurMapper(mapper: ExposureDetectionConfigMapper): ExposureDetectionConfig.Mapper =
mapper
fun exposureMapper(mapper: ExposureDetectionConfigMapper):
ExposureDetectionConfig.Mapper = mapper
@Provides
fun riskMapper(mapper: RiskCalculationConfigMapper): RiskCalculationConfig.Mapper = mapper
fun windowRiskMapper(mapper: ExposureWindowRiskCalculationConfigMapper):
ExposureWindowRiskCalculationConfig.Mapper = mapper
companion object {
private val HTTP_TIMEOUT_APPCONFIG = Duration.standardSeconds(10)
......
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<String>
val appFeatureus: AppFeaturesOuterClass.AppFeatures
val appFeatures: AppFeaturesOuterClass.AppFeatures
interface Mapper : ConfigMapper<CWAConfig>
}
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<ExposureDetectionConfig>
......
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<RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter>
val minutesAtAttenuationWeights: List<RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight>
val transmissionRiskLevelEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding
val transmissionRiskLevelFilters: List<RiskCalculationParametersOuterClass.TrlFilter>
val transmissionRiskLevelMultiplier: Double
val normalizedTimePerExposureWindowToRiskLevelMapping:
List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>
val normalizedTimePerDayToRiskLevelMappingList:
List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>
interface Mapper {
fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): ExposureWindowRiskCalculationConfig
}
}
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<RiskCalculationConfig>
}
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<ResponseBody>
}
......@@ -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<String> =
internal fun AppConfigAndroid.ApplicationConfigurationAndroid.getMappedSupportedCountries(): List<String> =
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<String>,
override val appFeatureus: AppFeaturesOuterClass.AppFeatures
override val appFeatures: AppFeaturesOuterClass.AppFeatures
) : CWAConfig
companion object {
......
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<T> {
fun map(rawConfig: AppConfig.ApplicationConfiguration): T
fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): T
}
......@@ -2,16 +2,16 @@ 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
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
}
......@@ -3,9 +3,9 @@ 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,7 +14,7 @@ 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 {
......@@ -24,7 +24,7 @@ class ConfigParser @Inject constructor(
cwaConfig = cwaConfigMapper.map(it),
keyDownloadConfig = keyDownloadConfigMapper.map(it),
exposureDetectionConfig = exposureDetectionConfigMapper.map(it),
riskCalculationConfig = riskCalculationConfigMapper.map(it)
exposureWindowRiskCalculationConfig = exposureWindowRiskCalculationConfigMapper.map(it)
)
}
} catch (e: Exception) {
......@@ -32,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)
}
}
......@@ -2,18 +2,18 @@ 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
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
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 = if (rawConfig.hasAndroidExposureDetectionParameters()) {
rawConfig.androidExposureDetectionParameters
override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): ExposureDetectionConfig {
val exposureParams = if (rawConfig.hasExposureDetectionParameters()) {
rawConfig.exposureDetectionParameters
} else {
null
}
return ExposureDetectionConfigContainer(
exposureDetectionConfiguration = rawConfig.mapRiskScoreToExposureConfiguration(),
exposureDetectionParameters = exposureParams,
maxExposureDetectionsPerUTCDay = exposureParams.maxExposureDetectionsPerDay(),
minTimeBetweenDetections = exposureParams.minTimeBetweenExposureDetections(),
......@@ -27,7 +25,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,
......@@ -62,54 +59,3 @@ fun ExposureDetectionParametersAndroid?.minTimeBetweenExposureDetections(): Dura
(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()
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
@Reusable
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,
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<RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter>,
override val minutesAtAttenuationWeights: List<RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight>,
override val transmissionRiskLevelEncoding: RiskCalculationParametersOuterClass.TransmissionRiskLevelEncoding,
override val transmissionRiskLevelFilters: List<RiskCalculationParametersOuterClass.TrlFilter>,
override val transmissionRiskLevelMultiplier: Double,
override val normalizedTimePerExposureWindowToRiskLevelMapping:
List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>,
override val normalizedTimePerDayToRiskLevelMappingList:
List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>
) : ExposureWindowRiskCalculationConfig
}
......@@ -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,9 +15,9 @@ import javax.inject.Inject
@Reusable
class KeyDownloadParametersMapper @Inject constructor() : KeyDownloadConfig.Mapper {
override fun map(rawConfig: AppConfig.ApplicationConfiguration): KeyDownloadConfig {
val rawParameters = if (rawConfig.hasAndroidKeyDownloadParameters()) {
rawConfig.androidKeyDownloadParameters
override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): KeyDownloadConfig {
val rawParameters = if (rawConfig.hasKeyDownloadParameters()) {
rawConfig.keyDownloadParameters
} else {
null
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment