diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/storage/ExposureSummaryDaoTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/storage/ExposureSummaryDaoTest.kt deleted file mode 100644 index 32df9c31bd5..00000000000 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/storage/ExposureSummaryDaoTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package de.rki.coronawarnapp.storage - -import android.content.Context -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -/** - * ExposureSummaryDao test. - */ -@RunWith(AndroidJUnit4::class) -class ExposureSummaryDaoTest { - private lateinit var dao: ExposureSummaryDao - private lateinit var db: AppDatabase - - @Before - fun setUp() { - val context = ApplicationProvider.getApplicationContext() - db = Room.inMemoryDatabaseBuilder( - context, AppDatabase::class.java - ).build() - dao = db.exposureSummaryDao() - } - - /** - * Test Create / Read DB operations. - */ - @Test - fun testCROperations() { - runBlocking { - val testEntity1 = ExposureSummaryEntity().apply { - this.daysSinceLastExposure = 1 - this.matchedKeyCount = 1 - this.maximumRiskScore = 1 - this.summationRiskScore = 1 - } - - val testEntity2 = ExposureSummaryEntity().apply { - this.daysSinceLastExposure = 2 - this.matchedKeyCount = 2 - this.maximumRiskScore = 2 - this.summationRiskScore = 2 - } - - assertThat(dao.getExposureSummaryEntities().isEmpty()).isTrue() - - val id1 = dao.insertExposureSummaryEntity(testEntity1) - var selectAll = dao.getExposureSummaryEntities() - var selectLast = dao.getLatestExposureSummary() - assertThat(dao.getExposureSummaryEntities().isEmpty()).isFalse() - assertThat(selectAll.size).isEqualTo(1) - assertThat(selectAll[0].id).isEqualTo(id1) - assertThat(selectLast).isNotNull() - assertThat(selectLast?.id).isEqualTo(id1) - - val id2 = dao.insertExposureSummaryEntity(testEntity2) - selectAll = dao.getExposureSummaryEntities() - selectLast = dao.getLatestExposureSummary() - assertThat(selectAll.isEmpty()).isFalse() - assertThat(selectAll.size).isEqualTo(2) - assertThat(selectLast).isNotNull() - assertThat(selectLast?.id).isEqualTo(id2) - } - } - - @After - fun closeDb() { - db.close() - } -} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt index 694615a9ae2..c03b84f3902 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt @@ -16,7 +16,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? = 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 @@ -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() 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 = diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt index f9834cdc792..b0e40407ce9 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt @@ -1,18 +1,13 @@ package de.rki.coronawarnapp.test.risklevel.ui -import android.content.Intent import android.os.Bundle import android.view.View import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.navArgs -import com.google.zxing.integration.android.IntentIntegrator -import com.google.zxing.integration.android.IntentResult import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentTestRiskLevelCalculationBinding -import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange -import de.rki.coronawarnapp.sharing.ExposureSharingService import de.rki.coronawarnapp.test.menu.ui.TestMenuItem import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel import de.rki.coronawarnapp.util.di.AutoInject @@ -20,7 +15,6 @@ import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted -import timber.log.Timber import javax.inject.Inject @Suppress("LongMethod") @@ -55,7 +49,6 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le } binding.buttonRetrieveDiagnosisKeys.setOnClickListener { vm.retrieveDiagnosisKeys() } - binding.buttonProvideKeyViaQr.setOnClickListener { vm.scanLocalQRCodeAndProvide() } binding.buttonCalculateRiskLevel.setOnClickListener { vm.calculateRiskLevel() } binding.buttonClearDiagnosisKeyCache.setOnClickListener { vm.clearKeyCache() } @@ -67,66 +60,32 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le ).show() } - vm.riskScoreState.observe2(this) { state -> - binding.labelRiskScore.text = state.riskScoreMsg - binding.labelBackendParameters.text = state.backendParameters - binding.labelExposureSummary.text = state.exposureSummary - binding.labelFormula.text = state.formula - binding.labelExposureInfo.text = state.exposureInfo + vm.additionalRiskCalcInfo.observe2(this) { + binding.labelRiskAdditionalInfo.text = it } - vm.startENFObserver() - vm.apiKeysProvidedEvent.observe2(this) { event -> - Toast.makeText( - requireContext(), - "Provided ${event.keyCount} keys to Google API 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 ) } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index 784425798c2..24e49d8754a 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -1,24 +1,23 @@ package de.rki.coronawarnapp.test.risklevel.ui import android.content.Context -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData -import com.google.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() val riskLevelResetEvent = SingleLiveEvent() - val apiKeysProvidedEvent = SingleLiveEvent() - val riskScoreState = MutableLiveData(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 = - 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.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 - 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() } } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml index a4eb49227c1..30b10c80b5a 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml @@ -61,36 +61,6 @@ android:layout_marginBottom="@dimen/spacing_tiny" android:text="@string/test_api_exposure_summary_headline" /> - - - - - - - - - -