Skip to content
Snippets Groups Projects
Unverified Commit df8d0057 authored by BMItter's avatar BMItter Committed by GitHub
Browse files

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
parent 082a2c22
No related branches found
No related tags found
No related merge requests found
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
)
}
......
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<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)
......@@ -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>()
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<File>
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() }
}
......
......@@ -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>
......
......@@ -80,7 +80,7 @@ object RiskLevelRepository {
*
* @return
*/
private fun getLastSuccessfullyCalculatedScore(): RiskLevel =
fun getLastSuccessfullyCalculatedScore(): RiskLevel =
LocalData.lastSuccessfullyCalculatedRiskLevel()
/**
......
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