Skip to content
Snippets Groups Projects
Unverified Commit d191fc01 authored by Ralf Gehrer's avatar Ralf Gehrer Committed by GitHub
Browse files

Merge branch 1.8.x into 1.9.x (DEV) #1692

Merge branch 1.8.x into 1.9.x (DEV)
parents 12c61c94 c590c846
No related branches found
No related tags found
No related merge requests found
Showing
with 284 additions and 657 deletions
...@@ -223,9 +223,6 @@ jobs: ...@@ -223,9 +223,6 @@ jobs:
- run-gradle-cmd: - run-gradle-cmd:
desc: JaCoCo report desc: JaCoCo report
cmd: ":Corona-Warn-App:jacocoTestReportDeviceRelease -i" cmd: ":Corona-Warn-App:jacocoTestReportDeviceRelease -i"
- run:
name: Skip SonarCloud for external Pull Requests
command: '[[ -v CIRCLE_PR_REPONAME ]] && circleci-agent step halt || true'
- scan-sonar - scan-sonar
quick_build_device_for_testers_signed: quick_build_device_for_testers_signed:
executor: android/android executor: android/android
...@@ -275,9 +272,10 @@ jobs: ...@@ -275,9 +272,10 @@ jobs:
- run: - run:
name: Send to T-System name: Send to T-System
command: | command: |
fileName=$(find Corona-Warn-App/build/outputs/apk/deviceForTesters/release -name '*Corona-Warn-App*.apk')
curl --location --request POST $tsystems_upload_url \ curl --location --request POST $tsystems_upload_url \
--header "Authorization: Bearer $tsystems_upload_bearer" \ --header "Authorization: Bearer $tsystems_upload_bearer" \
--form 'file=@Corona-Warn-App/build/outputs/apk/deviceForTesters/release/Corona-Warn-App-deviceForTesters-release.apk' \ --form "file=@${fileName}" \
workflows: workflows:
version: 2 version: 2
quick_build: quick_build:
......
...@@ -3,10 +3,10 @@ name: "Validate Gradle Wrapper" ...@@ -3,10 +3,10 @@ name: "Validate Gradle Wrapper"
on: on:
push: push:
branches: branches:
- master - main
pull_request: pull_request:
branches: branches:
- master - main
jobs: jobs:
validation: validation:
......
...@@ -163,11 +163,12 @@ android { ...@@ -163,11 +163,12 @@ android {
} }
println("deviceForTesters adjusted versionName: $adjustedVersionName") println("deviceForTesters adjusted versionName: $adjustedVersionName")
} }
if (flavor.name != "device") {
variant.outputs.each { output -> variant.outputs.each { output ->
def apkName = "Corona-Warn-App-${output.versionNameOverride}-${flavor.name}-${variant.buildType.name}.apk" def apkName = "Corona-Warn-App-${output.versionNameOverride}-${flavor.name}-${variant.buildType.name}.apk"
println("APK Name: $apkName") println("Override APK Name: $apkName")
output.outputFileName = apkName output.outputFileName = apkName
}
} }
} }
......
...@@ -72,9 +72,6 @@ ...@@ -72,9 +72,6 @@
-dontwarn sun.misc.** -dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; } #-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, # Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter -keep class * extends com.google.gson.TypeAdapter
...@@ -87,4 +84,6 @@ ...@@ -87,4 +84,6 @@
@com.google.gson.annotations.SerializedName <fields>; @com.google.gson.annotations.SerializedName <fields>;
} }
##---------------End: proguard configuration for Gson ---------- ##---------------End: proguard configuration for Gson ----------
\ No newline at end of file
-keep class de.rki.coronawarnapp.server.protocols.internal.** { *; }
\ No newline at end of file
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()
}
}
package de.rki.coronawarnapp.test
import android.content.Context
import android.text.format.Formatter
import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask
import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask.Progress.ApiSubmissionFinished
import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask.Progress.ApiSubmissionStarted
import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask.Progress.KeyFilesDownloadFinished
import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask.Progress.KeyFilesDownloadStarted
import de.rki.coronawarnapp.risk.RiskLevelTask
import de.rki.coronawarnapp.task.Task
import de.rki.coronawarnapp.task.common.DefaultTaskRequest
import de.rki.coronawarnapp.task.submitAndListen
import de.rki.coronawarnapp.util.di.AppInjector
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import timber.log.Timber
import java.util.UUID
class RiskLevelAndKeyRetrievalBenchmark(
private val context: Context,
private val countries: List<String>
) {
/**
* the key cache instance used to store queried dates and hours
*/
private val keyCache = AppInjector.component.keyCacheRepository
/**
* Calls the RetrieveDiagnosisKeysTransaction and RiskLevelTransaction and measures them.
* Results are displayed using a label
* @param callCount defines how often the transactions should be called (each call will be
* measured separately)
*/
suspend fun start(
callCount: Int,
onBenchmarkCompletedListener: OnBenchmarkCompletedListener
) {
var resultInfo = StringBuilder()
.append(
"MEASUREMENT Running for Countries:\n " +
"${countries.joinToString(", ")}\n\n"
)
.append("Result: \n\n")
.append("#\t Combined \t Download \t Sub \t Risk \t File # \t F. size\n")
onBenchmarkCompletedListener(resultInfo.toString())
repeat(callCount) { index ->
keyCache.clear()
var keyRetrievalError = ""
var keyFileCount: Int = -1
var keyFileDownloadDuration: Long = -1
var keyFilesSize: Long = -1
var apiSubmissionDuration: Long = -1
measureDiagnosticKeyRetrieval(
label = "#$index",
countries = countries,
downloadFinished = { duration, keyCount, totalFileSize ->
keyFileCount = keyCount
keyFileDownloadDuration = duration
keyFilesSize = totalFileSize
}, apiSubmissionFinished = { duration ->
apiSubmissionDuration = duration
})
var calculationDuration: Long = -1
var calculationError = ""
measureKeyCalculation("#$index") {
if (it != null) calculationDuration = it
// build result entry for current iteration with all gathered data
resultInfo.append(
"${index + 1}. \t ${calculationDuration + keyFileDownloadDuration + apiSubmissionDuration} ms \t " +
"$keyFileDownloadDuration ms " + "\t $apiSubmissionDuration ms" +
"\t $calculationDuration ms \t $keyFileCount \t " +
"${Formatter.formatFileSize(context, keyFilesSize)}\n"
)
if (keyRetrievalError.isNotEmpty()) {
resultInfo.append("Key Retrieval Error: $keyRetrievalError\n")
}
if (calculationError.isNotEmpty()) {
resultInfo.append("Calculation Error: $calculationError\n")
}
onBenchmarkCompletedListener(resultInfo.toString())
}
}
}
private suspend fun measureKeyCalculation(label: String, callback: (Long?) -> Unit) {
val uuid = UUID.randomUUID()
val t0 = System.currentTimeMillis()
AppInjector.component.taskController.tasks
.map {
it
.map { taskInfo -> taskInfo.taskState }
.filter { taskState -> taskState.request.id == uuid && taskState.isFinished }
}
.collect {
it.firstOrNull()?.also { state ->
Timber.v("MEASURE [Risk Level Calculation] $label finished")
callback.invoke(
if (state.error != null)
null
else
System.currentTimeMillis() - t0
)
}
}
Timber.v("MEASURE [Risk Level Calculation] $label started")
AppInjector.component.taskController.submit(
DefaultTaskRequest(
RiskLevelTask::class,
object : Task.Arguments {},
uuid
)
)
}
private suspend fun measureDiagnosticKeyRetrieval(
label: String,
countries: List<String>,
downloadFinished: (duration: Long, keyCount: Int, fileSize: Long) -> Unit,
apiSubmissionFinished: (duration: Long) -> Unit
) {
var keyFileDownloadStart: Long = -1
var apiSubmissionStarted: Long = -1
AppInjector.component.taskController.submitAndListen(
DefaultTaskRequest(DownloadDiagnosisKeysTask::class, DownloadDiagnosisKeysTask.Arguments(countries))
).collect { progress: Task.Progress ->
when (progress) {
is KeyFilesDownloadStarted -> {
Timber.v("MEASURE [Diagnostic Key Files] $label started")
keyFileDownloadStart = System.currentTimeMillis()
}
is KeyFilesDownloadFinished -> {
Timber.v("MEASURE [Diagnostic Key Files] $label finished")
val duration = System.currentTimeMillis() - keyFileDownloadStart
downloadFinished(duration, progress.keyCount, progress.fileSize)
}
is ApiSubmissionStarted -> {
apiSubmissionStarted = System.currentTimeMillis()
}
is ApiSubmissionFinished -> {
val duration = System.currentTimeMillis() - apiSubmissionStarted
apiSubmissionFinished(duration)
}
}
}
}
}
typealias OnBenchmarkCompletedListener = (resultInfo: String) -> Unit
...@@ -3,11 +3,13 @@ package de.rki.coronawarnapp.test.api.ui ...@@ -3,11 +3,13 @@ package de.rki.coronawarnapp.test.api.ui
import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.CWADebug
data class LoggerState( data class LoggerState(
val isLogging: Boolean val isLogging: Boolean,
val logsize: Long
) { ) {
companion object { companion object {
internal fun CWADebug.toLoggerState() = LoggerState( internal fun CWADebug.toLoggerState() = LoggerState(
isLogging = fileLogger?.isLogging ?: false isLogging = fileLogger?.isLogging ?: false,
logsize = fileLogger?.logFile?.length() ?: 0L
) )
} }
} }
...@@ -16,7 +16,6 @@ import androidx.lifecycle.lifecycleScope ...@@ -16,7 +16,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient 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.gms.nearby.exposurenotification.TemporaryExposureKey
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson import com.google.gson.Gson
...@@ -32,25 +31,24 @@ import de.rki.coronawarnapp.exception.ExceptionCategory ...@@ -32,25 +31,24 @@ import de.rki.coronawarnapp.exception.ExceptionCategory
import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL
import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.TransactionException
import de.rki.coronawarnapp.exception.reporting.report 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.nearby.InternalExposureNotificationPermissionHelper
import de.rki.coronawarnapp.receiver.ExposureStateUpdateReceiver import de.rki.coronawarnapp.receiver.ExposureStateUpdateReceiver
import de.rki.coronawarnapp.risk.ExposureResultStore
import de.rki.coronawarnapp.risk.TimeVariables import de.rki.coronawarnapp.risk.TimeVariables
import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange
import de.rki.coronawarnapp.sharing.ExposureSharingService import de.rki.coronawarnapp.sharing.ExposureSharingService
import de.rki.coronawarnapp.storage.AppDatabase 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.storage.tracing.TracingIntervalRepository
import de.rki.coronawarnapp.test.menu.ui.TestMenuItem import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
import de.rki.coronawarnapp.util.KeyFileHelper 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.di.AutoInject
import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.observe2
import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.ui.viewBindingLazy
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.joda.time.DateTime import org.joda.time.DateTime
...@@ -66,6 +64,8 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -66,6 +64,8 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
InternalExposureNotificationPermissionHelper.Callback, AutoInject { InternalExposureNotificationPermissionHelper.Callback, AutoInject {
@Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
@Inject lateinit var enfClient: ENFClient
@Inject lateinit var exposureResultStore: ExposureResultStore
private val vm: TestForApiFragmentViewModel by cwaViewModels { viewModelFactory } private val vm: TestForApiFragmentViewModel by cwaViewModels { viewModelFactory }
companion object { companion object {
...@@ -85,10 +85,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -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 myExposureKeysJSON: String? = null
private var myExposureKeys: List<TemporaryExposureKey>? = mutableListOf() private var myExposureKeys: List<TemporaryExposureKey>? = mutableListOf()
private var otherExposureKey: AppleLegacyKeyExchange.Key? = null private var otherExposureKey: AppleLegacyKeyExchange.Key? = null
...@@ -96,8 +92,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -96,8 +92,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
private lateinit var internalExposureNotificationPermissionHelper: InternalExposureNotificationPermissionHelper private lateinit var internalExposureNotificationPermissionHelper: InternalExposureNotificationPermissionHelper
private var token: String? = null
private lateinit var qrPager: ViewPager2 private lateinit var qrPager: ViewPager2
private lateinit var qrPagerAdapter: RecyclerView.Adapter<QRPagerAdapter.QRViewHolder> private lateinit var qrPagerAdapter: RecyclerView.Adapter<QRPagerAdapter.QRViewHolder>
...@@ -108,8 +102,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -108,8 +102,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
token = UUID.randomUUID().toString()
internalExposureNotificationPermissionHelper = internalExposureNotificationPermissionHelper =
InternalExposureNotificationPermissionHelper(this, this) InternalExposureNotificationPermissionHelper(this, this)
...@@ -127,7 +119,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -127,7 +119,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
binding.apply { binding.apply {
buttonApiTestStart.setOnClickListener { start() } buttonApiTestStart.setOnClickListener { start() }
buttonApiGetExposureKeys.setOnClickListener { getExposureKeys() } buttonApiGetExposureKeys.setOnClickListener { getExposureKeys() }
buttonApiGetCheckExposure.setOnClickListener { checkExposure() }
buttonApiScanQrCode.setOnClickListener { buttonApiScanQrCode.setOnClickListener {
IntentIntegrator.forSupportFragment(this@TestForAPIFragment) IntentIntegrator.forSupportFragment(this@TestForAPIFragment)
...@@ -168,8 +159,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -168,8 +159,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
buttonRetrieveExposureSummary.setOnClickListener { buttonRetrieveExposureSummary.setOnClickListener {
vm.launch { vm.launch {
val summary = ExposureSummaryRepository.getExposureSummaryRepository() val summary = exposureResultStore.entities.first().exposureWindows.toString()
.getExposureSummaryEntities().toString()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
showToast(summary) showToast(summary)
...@@ -206,12 +196,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -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 -> private val prettyKey = { key: AppleLegacyKeyExchange.Key ->
StringBuilder() StringBuilder()
.append("\nKey data: ${key.keyData}") .append("\nKey data: ${key.keyData}")
...@@ -274,9 +258,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -274,9 +258,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
if (null == otherExposureKey) { if (null == otherExposureKey) {
showToast("No other keys provided. Please fill the EditText with the JSON containing keys") showToast("No other keys provided. Please fill the EditText with the JSON containing keys")
} else { } else {
token = UUID.randomUUID().toString()
LocalData.googleApiToken(token)
val appleKeyList = mutableListOf<AppleLegacyKeyExchange.Key>() val appleKeyList = mutableListOf<AppleLegacyKeyExchange.Key>()
for (key in otherExposureKeyList) { for (key in otherExposureKeyList) {
...@@ -298,7 +279,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -298,7 +279,7 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
val dir = File( val dir = File(
File(requireContext().getExternalFilesDir(null), "key-export"), File(requireContext().getExternalFilesDir(null), "key-export"),
token ?: "" UUID.randomUUID().toString()
) )
dir.mkdirs() dir.mkdirs()
...@@ -306,15 +287,13 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -306,15 +287,13 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i),
lifecycleScope.launch { lifecycleScope.launch {
googleFileList = KeyFileHelper.asyncCreateExportFiles(appleFiles, dir) 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 { try {
// only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API
enfClient.provideDiagnosisKeys( enfClient.provideDiagnosisKeys(
googleFileList, googleFileList
AppInjector.component.appConfigProvider.getAppConfig().exposureDetectionConfiguration,
token!!
) )
showToast("Provided ${appleKeyList.size} keys to Google API with token $token") showToast("Provided ${appleKeyList.size} keys to Google API")
} catch (e: Exception) { } catch (e: Exception) {
e.report(ExceptionCategory.EXPOSURENOTIFICATION) e.report(ExceptionCategory.EXPOSURENOTIFICATION)
} }
...@@ -322,51 +301,6 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), ...@@ -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() { private fun updateKeysDisplay() {
val myKeys = val myKeys =
......
...@@ -18,7 +18,7 @@ class TestForApiFragmentViewModel @AssistedInject constructor( ...@@ -18,7 +18,7 @@ class TestForApiFragmentViewModel @AssistedInject constructor(
) : CWAViewModel() { ) : CWAViewModel() {
fun calculateRiskLevelClicked() { fun calculateRiskLevelClicked() {
taskController.submit(DefaultTaskRequest(RiskLevelTask::class)) taskController.submit(DefaultTaskRequest(RiskLevelTask::class, originTag = "TestForApiFragmentViewModel"))
} }
val gmsState by smartLiveData { val gmsState by smartLiveData {
......
...@@ -32,12 +32,9 @@ class AppConfigTestFragment : Fragment(R.layout.fragment_test_appconfig), AutoIn ...@@ -32,12 +32,9 @@ class AppConfigTestFragment : Fragment(R.layout.fragment_test_appconfig), AutoIn
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
vm.currentConfig.observe2(this) { data -> vm.currentConfig.observe2(this) { data ->
binding.currentConfiguration.text = binding.currentConfiguration.text = data.rawConfig.toString()
data?.rawConfig?.toString() ?: "No config available." binding.lastUpdate.text = timeFormatter.print(data.updatedAt)
binding.lastUpdate.text = data?.updatedAt?.let { timeFormatter.print(it) } ?: "n/a" binding.timeOffset.text = "${data.localOffset.millis}ms (configType=${data.configType})"
binding.timeOffset.text = data?.let {
"${it.localOffset.millis}ms (configType=${it.configType})"
} ?: "n/a"
} }
vm.errorEvent.observe2(this) { vm.errorEvent.observe2(this) {
......
...@@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData ...@@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.bugreporting.event.BugEvent import de.rki.coronawarnapp.bugreporting.event.BugEvent
import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.bugreporting.reportProblem
...@@ -12,9 +11,7 @@ import de.rki.coronawarnapp.bugreporting.storage.repository.BugRepository ...@@ -12,9 +11,7 @@ import de.rki.coronawarnapp.bugreporting.storage.repository.BugRepository
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.lang.Exception
class SettingsCrashReportViewModel @AssistedInject constructor( class SettingsCrashReportViewModel @AssistedInject constructor(
private val crashReportRepository: BugRepository private val crashReportRepository: BugRepository
...@@ -28,7 +25,7 @@ class SettingsCrashReportViewModel @AssistedInject constructor( ...@@ -28,7 +25,7 @@ class SettingsCrashReportViewModel @AssistedInject constructor(
createBugEventFormattedText(it) createBugEventFormattedText(it)
} }
fun deleteAllCrashReports() = viewModelScope.launch(Dispatchers.IO) { fun deleteAllCrashReports() = launch(Dispatchers.IO) {
crashReportRepository.clear() crashReportRepository.clear()
} }
......
...@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.test.debugoptions.ui ...@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.test.debugoptions.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.text.format.Formatter
import android.view.View import android.view.View
import android.widget.RadioButton import android.widget.RadioButton
import android.widget.RadioGroup import android.widget.RadioGroup
...@@ -31,24 +32,14 @@ class DebugOptionsFragment : Fragment(R.layout.fragment_test_debugoptions), Auto ...@@ -31,24 +32,14 @@ class DebugOptionsFragment : Fragment(R.layout.fragment_test_debugoptions), Auto
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Debug card
binding.backgroundNotificationsToggle.apply {
setOnClickListener { vm.setBackgroundNotifications(isChecked) }
}
vm.backgroundNotificationsToggleEvent.observe2(this@DebugOptionsFragment) {
showSnackBar("Background Notifications are activated: $it")
}
vm.debugOptionsState.observe2(this) { state ->
binding.apply {
backgroundNotificationsToggle.isChecked = state.areNotificationsEnabled
}
}
binding.testLogfileToggle.apply { binding.testLogfileToggle.apply {
setOnClickListener { vm.setLoggerEnabled(isChecked) } setOnClickListener { vm.setLoggerEnabled(isChecked) }
} }
vm.loggerState.observe2(this) { state -> vm.loggerState.observe2(this) { state ->
binding.apply { binding.apply {
testLogfileToggle.isChecked = state.isLogging testLogfileToggle.isChecked = state.isLogging
val logSize = Formatter.formatShortFileSize(requireContext(), state.logsize)
testLogfileToggle.text = "Logfile enabled ($logSize)"
testLogfileShare.setGone(!state.isLogging) testLogfileShare.setGone(!state.isLogging)
} }
} }
......
package de.rki.coronawarnapp.test.debugoptions.ui package de.rki.coronawarnapp.test.debugoptions.ui
import android.content.Context import android.content.Context
import androidx.lifecycle.viewModelScope
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.environment.EnvironmentSetup
import de.rki.coronawarnapp.environment.EnvironmentSetup.Type.Companion.toEnvironmentType import de.rki.coronawarnapp.environment.EnvironmentSetup.Type.Companion.toEnvironmentType
import de.rki.coronawarnapp.risk.RiskLevelTask
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.TestSettings
import de.rki.coronawarnapp.task.TaskController
import de.rki.coronawarnapp.task.common.DefaultTaskRequest
import de.rki.coronawarnapp.test.api.ui.EnvironmentState.Companion.toEnvironmentState import de.rki.coronawarnapp.test.api.ui.EnvironmentState.Companion.toEnvironmentState
import de.rki.coronawarnapp.test.api.ui.LoggerState.Companion.toLoggerState import de.rki.coronawarnapp.test.api.ui.LoggerState.Companion.toLoggerState
import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.CWADebug
...@@ -19,24 +13,14 @@ import de.rki.coronawarnapp.util.ui.SingleLiveEvent ...@@ -19,24 +13,14 @@ import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.ui.smartLiveData import de.rki.coronawarnapp.util.ui.smartLiveData
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File import java.io.File
class DebugOptionsFragmentViewModel @AssistedInject constructor( class DebugOptionsFragmentViewModel @AssistedInject constructor(
@AppContext private val context: Context, @AppContext private val context: Context,
private val envSetup: EnvironmentSetup, private val envSetup: EnvironmentSetup,
private val testSettings: TestSettings,
private val taskController: TaskController,
dispatcherProvider: DispatcherProvider dispatcherProvider: DispatcherProvider
) : CWAViewModel(dispatcherProvider = dispatcherProvider) { ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
val debugOptionsState by smartLiveData {
DebugOptionsState(
areNotificationsEnabled = LocalData.backgroundNotification()
)
}
val environmentState by smartLiveData { val environmentState by smartLiveData {
envSetup.toEnvironmentState() envSetup.toEnvironmentState()
} }
...@@ -50,16 +34,6 @@ class DebugOptionsFragmentViewModel @AssistedInject constructor( ...@@ -50,16 +34,6 @@ class DebugOptionsFragmentViewModel @AssistedInject constructor(
} }
} }
val backgroundNotificationsToggleEvent = SingleLiveEvent<Boolean>()
fun setBackgroundNotifications(enabled: Boolean) {
debugOptionsState.update {
LocalData.backgroundNotification(enabled)
it.copy(areNotificationsEnabled = enabled)
}
backgroundNotificationsToggleEvent.postValue(enabled)
}
val loggerState by smartLiveData { val loggerState by smartLiveData {
CWADebug.toLoggerState() CWADebug.toLoggerState()
} }
...@@ -71,15 +45,11 @@ class DebugOptionsFragmentViewModel @AssistedInject constructor( ...@@ -71,15 +45,11 @@ class DebugOptionsFragmentViewModel @AssistedInject constructor(
loggerState.update { CWADebug.toLoggerState() } loggerState.update { CWADebug.toLoggerState() }
} }
fun calculateRiskLevelClicked() {
taskController.submit(DefaultTaskRequest(RiskLevelTask::class))
}
val logShareEvent = SingleLiveEvent<File?>() val logShareEvent = SingleLiveEvent<File?>()
fun shareLogFile() { fun shareLogFile() {
CWADebug.fileLogger?.let { CWADebug.fileLogger?.let {
viewModelScope.launch(context = Dispatchers.Default) { launch {
if (!it.logFile.exists()) return@launch if (!it.logFile.exists()) return@launch
val externalPath = File( val externalPath = File(
......
package de.rki.coronawarnapp.test.debugoptions.ui
data class DebugOptionsState(
val areNotificationsEnabled: Boolean
)
...@@ -8,6 +8,7 @@ import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment ...@@ -8,6 +8,7 @@ import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment
import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment
import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment
import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment
import de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragment
import de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragment import de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragment
import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
...@@ -23,6 +24,7 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() { ...@@ -23,6 +24,7 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() {
TestRiskLevelCalculationFragment.MENU_ITEM, TestRiskLevelCalculationFragment.MENU_ITEM,
KeyDownloadTestFragment.MENU_ITEM, KeyDownloadTestFragment.MENU_ITEM,
TestTaskControllerFragment.MENU_ITEM, TestTaskControllerFragment.MENU_ITEM,
SubmissionTestFragment.MENU_ITEM,
SettingsCrashReportFragment.MENU_ITEM SettingsCrashReportFragment.MENU_ITEM
).let { MutableLiveData(it) } ).let { MutableLiveData(it) }
} }
......
package de.rki.coronawarnapp.test.risklevel.ui package de.rki.coronawarnapp.test.risklevel.ui
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.navArgs 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.R
import de.rki.coronawarnapp.databinding.FragmentTestRiskLevelCalculationBinding 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.test.menu.ui.TestMenuItem
import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.di.AutoInject
import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.observe2
import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.ui.viewBindingLazy
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@Suppress("LongMethod") @Suppress("LongMethod")
...@@ -39,7 +32,6 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le ...@@ -39,7 +32,6 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le
) )
private val settingsViewModel: SettingsViewModel by activityViewModels() private val settingsViewModel: SettingsViewModel by activityViewModels()
private val submissionViewModel: SubmissionViewModel by activityViewModels()
private val binding: FragmentTestRiskLevelCalculationBinding by viewBindingLazy() private val binding: FragmentTestRiskLevelCalculationBinding by viewBindingLazy()
...@@ -51,10 +43,12 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le ...@@ -51,10 +43,12 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le
} }
binding.settingsViewModel = settingsViewModel binding.settingsViewModel = settingsViewModel
binding.submissionViewModel = submissionViewModel
vm.showRiskStatusCard.observe2(this) {
binding.showRiskStatusCard = it
}
binding.buttonRetrieveDiagnosisKeys.setOnClickListener { vm.retrieveDiagnosisKeys() } binding.buttonRetrieveDiagnosisKeys.setOnClickListener { vm.retrieveDiagnosisKeys() }
binding.buttonProvideKeyViaQr.setOnClickListener { vm.scanLocalQRCodeAndProvide() }
binding.buttonCalculateRiskLevel.setOnClickListener { vm.calculateRiskLevel() } binding.buttonCalculateRiskLevel.setOnClickListener { vm.calculateRiskLevel() }
binding.buttonClearDiagnosisKeyCache.setOnClickListener { vm.clearKeyCache() } binding.buttonClearDiagnosisKeyCache.setOnClickListener { vm.clearKeyCache() }
...@@ -66,66 +60,32 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le ...@@ -66,66 +60,32 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le
).show() ).show()
} }
vm.riskScoreState.observe2(this) { state -> vm.additionalRiskCalcInfo.observe2(this) {
binding.labelRiskScore.text = state.riskScoreMsg binding.labelRiskAdditionalInfo.text = it
binding.labelBackendParameters.text = state.backendParameters
binding.labelExposureSummary.text = state.exposureSummary
binding.labelFormula.text = state.formula
binding.labelExposureInfo.text = state.exposureInfo
} }
vm.startENFObserver()
vm.apiKeysProvidedEvent.observe2(this) { event -> vm.aggregatedRiskResult.observe2(this) {
Toast.makeText( binding.labelAggregatedRiskResult.text = it
requireContext(),
"Provided ${event.keyCount} keys to Google API with token ${event.token}",
Toast.LENGTH_SHORT
).show()
} }
vm.startLocalQRCodeScanEvent.observe2(this) { vm.exposureWindowCountString.observe2(this) {
IntentIntegrator.forSupportFragment(this) binding.labelExposureWindowCount.text = it
.setOrientationLocked(false)
.setBeepEnabled(false)
.initiateScan()
} }
}
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) { vm.exposureWindows.observe2(this) {
Toast.makeText(requireContext(), "Cancelled", Toast.LENGTH_LONG).show() binding.labelExposureWindows.text = it
return
} }
ExposureSharingService.getOthersKeys(result.contents) { key: AppleLegacyKeyExchange.Key? -> vm.backendParameters.observe2(this) {
Timber.i("Keys scanned: %s", key) binding.labelBackendParameters.text = it
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)
} }
} }
companion object { companion object {
val TAG: String = TestRiskLevelCalculationFragment::class.simpleName!! val TAG: String = TestRiskLevelCalculationFragment::class.simpleName!!
val MENU_ITEM = TestMenuItem( val MENU_ITEM = TestMenuItem(
title = "Risklevel Calculation", title = "ENF v2 Calculation",
description = "Risklevel calculation related test options.", description = "Window Mode related overview.",
targetId = R.id.test_risklevel_calculation_fragment targetId = R.id.test_risklevel_calculation_fragment
) )
} }
......
package de.rki.coronawarnapp.test.risklevel.ui package de.rki.coronawarnapp.test.risklevel.ui
import android.content.Context import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.google.android.gms.nearby.exposurenotification.ExposureInformation
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject 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.download.DownloadDiagnosisKeysTask
import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository
import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.ExceptionCategory
import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.exception.reporting.report
import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.risk.ExposureResult
import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.risk.ExposureResultStore
import de.rki.coronawarnapp.risk.RiskLevel import de.rki.coronawarnapp.risk.RiskLevel
import de.rki.coronawarnapp.risk.RiskLevelTask import de.rki.coronawarnapp.risk.RiskLevelTask
import de.rki.coronawarnapp.risk.RiskLevels
import de.rki.coronawarnapp.risk.TimeVariables 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.AppDatabase
import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.RiskLevelRepository import de.rki.coronawarnapp.storage.RiskLevelRepository
import de.rki.coronawarnapp.storage.SubmissionRepository
import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.task.TaskController
import de.rki.coronawarnapp.task.common.DefaultTaskRequest import de.rki.coronawarnapp.task.common.DefaultTaskRequest
import de.rki.coronawarnapp.task.submitBlocking import de.rki.coronawarnapp.task.submitBlocking
import de.rki.coronawarnapp.ui.tracing.card.TracingCardStateProvider 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.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.di.AppContext 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.security.SecurityHelper
import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.joda.time.Instant
import timber.log.Timber import timber.log.Timber
import java.io.File import java.util.Date
import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
@Assisted private val handle: SavedStateHandle, @Assisted private val handle: SavedStateHandle,
@Assisted private val exampleArg: String?, @Assisted private val exampleArg: String?,
@AppContext private val context: Context, // App context @AppContext private val context: Context, // App context
dispatcherProvider: DispatcherProvider, dispatcherProvider: DispatcherProvider,
private val enfClient: ENFClient,
private val riskLevels: RiskLevels,
private val taskController: TaskController, private val taskController: TaskController,
private val keyCacheRepository: KeyCacheRepository, private val keyCacheRepository: KeyCacheRepository,
tracingCardStateProvider: TracingCardStateProvider private val appConfigProvider: AppConfigProvider,
tracingCardStateProvider: TracingCardStateProvider,
private val exposureResultStore: ExposureResultStore,
private val submissionRepository: SubmissionRepository
) : CWAViewModel( ) : CWAViewModel(
dispatcherProvider = dispatcherProvider dispatcherProvider = dispatcherProvider
) { ) {
val startLocalQRCodeScanEvent = SingleLiveEvent<Unit>() init {
Timber.d("CWAViewModel: %s", this)
Timber.d("SavedStateHandle: %s", handle)
Timber.d("Example arg: %s", exampleArg)
}
val riskLevelResetEvent = 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)
val tracingCardState = tracingCardStateProvider.state val tracingCardState = tracingCardStateProvider.state
.sample(150L) .sample(150L)
.asLiveData(dispatcherProvider.Default) .asLiveData(dispatcherProvider.Default)
init { val exposureWindowCountString = exposureResultStore
Timber.d("CWAViewModel: %s", this) .entities
Timber.d("SavedStateHandle: %s", handle) .map { "Retrieved ${it.exposureWindows.size} Exposure Windows" }
Timber.d("Example arg: %s", exampleArg) .asLiveData()
}
val exposureWindows = exposureResultStore
.entities
.map { if (it.exposureWindows.isEmpty()) "Exposure windows list is empty" else it.exposureWindows.toString() }
.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()
val additionalRiskCalcInfo = combine(
RiskLevelRepository.riskLevelScore,
RiskLevelRepository.riskLevelScoreLastSuccessfulCalculated,
exposureResultStore.matchedKeyCount,
exposureResultStore.daysSinceLastExposure,
LocalData.lastTimeDiagnosisKeysFromServerFetchFlow()
) { riskLevelScore,
riskLevelScoreLastSuccessfulCalculated,
matchedKeyCount,
daysSinceLastExposure,
lastTimeDiagnosisKeysFromServerFetch ->
createAdditionalRiskCalcInfo(
riskLevelScore = riskLevelScore,
riskLevelScoreLastSuccessfulCalculated = riskLevelScoreLastSuccessfulCalculated,
matchedKeyCount = matchedKeyCount,
daysSinceLastExposure = daysSinceLastExposure,
lastTimeDiagnosisKeysFromServerFetch = lastTimeDiagnosisKeysFromServerFetch
)
}.asLiveData()
private suspend fun createAdditionalRiskCalcInfo(
riskLevelScore: Int,
riskLevelScoreLastSuccessfulCalculated: Int,
matchedKeyCount: Int,
daysSinceLastExposure: Int,
lastTimeDiagnosisKeysFromServerFetch: Date?
): String = StringBuilder()
.appendLine("Risk Level: ${RiskLevel.forValue(riskLevelScore)}")
.appendLine("Last successful Risk Level: ${RiskLevel.forValue(riskLevelScoreLastSuccessfulCalculated)}")
.appendLine("Matched key count: $matchedKeyCount")
.appendLine("Days since last Exposure: $daysSinceLastExposure days")
.appendLine("Last Time Server Fetch: ${lastTimeDiagnosisKeysFromServerFetch?.time?.let { Instant.ofEpochMilli(it) }}")
.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() { fun retrieveDiagnosisKeys() {
Timber.d("Starting download diagnosis keys task")
launch { launch {
taskController.submitBlocking( taskController.submitBlocking(
DefaultTaskRequest(DownloadDiagnosisKeysTask::class, DownloadDiagnosisKeysTask.Arguments()) DefaultTaskRequest(
DownloadDiagnosisKeysTask::class,
DownloadDiagnosisKeysTask.Arguments(),
originTag = "TestRiskLevelCalculationFragmentCWAViewModel.retrieveDiagnosisKeys()"
)
) )
calculateRiskLevel()
} }
} }
fun calculateRiskLevel() { fun calculateRiskLevel() {
taskController.submit(DefaultTaskRequest(RiskLevelTask::class)) Timber.d("Starting calculate risk task")
taskController.submit(
DefaultTaskRequest(
RiskLevelTask::class,
originTag = "TestRiskLevelCalculationFragmentCWAViewModel.calculateRiskLevel()"
)
)
} }
fun resetRiskLevel() { fun resetRiskLevel() {
viewModelScope.launch { Timber.d("Resetting risk level")
launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
// Preference reset // Preference reset
...@@ -100,10 +201,11 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( ...@@ -100,10 +201,11 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
// Export File Reset // Export File Reset
keyCacheRepository.clear() keyCacheRepository.clear()
exposureResultStore.entities.value = ExposureResult(emptyList(), null)
LocalData.lastCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw) LocalData.lastCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw)
LocalData.lastSuccessfullyCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw) LocalData.lastSuccessfullyCalculatedRiskLevel(RiskLevel.UNDETERMINED.raw)
LocalData.lastTimeDiagnosisKeysFromServerFetch(null) LocalData.lastTimeDiagnosisKeysFromServerFetch(null)
LocalData.googleApiToken(null)
} catch (e: Exception) { } catch (e: Exception) {
e.report(ExceptionCategory.INTERNAL) e.report(ExceptionCategory.INTERNAL)
} }
...@@ -113,185 +215,9 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( ...@@ -113,185 +215,9 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
} }
} }
data class RiskScoreState(
val riskScoreMsg: String = "",
val backendParameters: String = "",
val exposureSummary: String = "",
val formula: String = "",
val exposureInfo: String = ""
)
fun startENFObserver() {
viewModelScope.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>
viewModelScope.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() { fun clearKeyCache() {
viewModelScope.launch { keyCacheRepository.clear() } Timber.d("Clearing key cache")
launch { keyCacheRepository.clear() }
} }
@AssistedInject.Factory @AssistedInject.Factory
......
package de.rki.coronawarnapp.test.submission.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentTestSubmissionBinding
import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
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 javax.inject.Inject
@SuppressLint("SetTextI18n")
class SubmissionTestFragment : Fragment(R.layout.fragment_test_submission), AutoInject {
@Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
private val vm: SubmissionTestFragmentViewModel by cwaViewModels { viewModelFactory }
private val binding: FragmentTestSubmissionBinding by viewBindingLazy()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
vm.currentTestId.observe2(this) {
binding.registrationTokenCurrent.text = "Current: '$it'"
}
binding.apply {
deleteTokenAction.setOnClickListener { vm.deleteRegistrationToken() }
scrambleTokenAction.setOnClickListener { vm.scrambleRegistrationToken() }
}
}
companion object {
val TAG: String = SubmissionTestFragment::class.simpleName!!
val MENU_ITEM = TestMenuItem(
title = "Submission Test Options",
description = "Submission related test options..",
targetId = R.id.test_submission_fragment
)
}
}
package de.rki.coronawarnapp.test.submission.ui
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
@Module
abstract class SubmissionTestFragmentModule {
@Binds
@IntoMap
@CWAViewModelKey(SubmissionTestFragmentViewModel::class)
abstract fun testKeyDownloadFragment(
factory: SubmissionTestFragmentViewModel.Factory
): CWAViewModelFactory<out CWAViewModel>
}
package de.rki.coronawarnapp.test.submission.ui
import androidx.lifecycle.asLiveData
import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import kotlinx.coroutines.flow.MutableStateFlow
import java.util.UUID
class SubmissionTestFragmentViewModel @AssistedInject constructor(
dispatcherProvider: DispatcherProvider
) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
private val internalToken = MutableStateFlow(LocalData.registrationToken())
val currentTestId = internalToken.asLiveData()
fun scrambleRegistrationToken() {
LocalData.registrationToken(UUID.randomUUID().toString())
internalToken.value = LocalData.registrationToken()
}
fun deleteRegistrationToken() {
LocalData.registrationToken(null)
internalToken.value = LocalData.registrationToken()
}
@AssistedInject.Factory
interface Factory : SimpleCWAViewModelFactory<SubmissionTestFragmentViewModel>
}
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