Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Fix refresh check and improve test menu (EXPOSUREAPP-4049) #1772

Merged
merged 5 commits into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import android.os.Bundle
import android.view.View
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.Toast
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentTestRiskLevelCalculationBinding
import de.rki.coronawarnapp.storage.TestSettings
Expand Down Expand Up @@ -59,12 +59,9 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le
binding.buttonClearDiagnosisKeyCache.setOnClickListener { vm.clearKeyCache() }
binding.buttonResetRiskLevel.setOnClickListener { vm.resetRiskLevel() }
binding.buttonExposureWindowsShare.setOnClickListener { vm.shareExposureWindows() }
vm.riskLevelResetEvent.observe2(this) {
Toast.makeText(
requireContext(), "Reset done, please fetch diagnosis keys from server again",
Toast.LENGTH_SHORT
).show()
}

vm.dataResetEvent.observe2(this) { Snackbar.make(requireView(), it, Snackbar.LENGTH_SHORT).show() }

vm.additionalRiskCalcInfo.observe2(this) {
binding.labelRiskAdditionalInfo.text = it
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.appconfig.ConfigData
import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask
import de.rki.coronawarnapp.diagnosiskeys.download.KeyPackageSyncSettings
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.modules.detectiontracker.ExposureDetectionTracker
import de.rki.coronawarnapp.nearby.modules.detectiontracker.latestSubmission
import de.rki.coronawarnapp.risk.RiskLevelTask
import de.rki.coronawarnapp.risk.RiskState
import de.rki.coronawarnapp.risk.TimeVariables
import de.rki.coronawarnapp.risk.result.AggregatedRiskResult
import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
import de.rki.coronawarnapp.storage.AppDatabase
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.storage.SubmissionRepository
import de.rki.coronawarnapp.storage.TestSettings
import de.rki.coronawarnapp.task.TaskController
Expand All @@ -32,21 +31,17 @@ import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
import de.rki.coronawarnapp.util.TimeStamper
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.security.SecurityHelper
import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.withContext
import org.joda.time.Instant
import org.joda.time.format.DateTimeFormat
import timber.log.Timber
import java.io.File
import java.util.Date
import java.util.concurrent.TimeUnit

class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
Expand All @@ -60,7 +55,9 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
tracingCardStateProvider: TracingCardStateProvider,
private val riskLevelStorage: RiskLevelStorage,
private val testSettings: TestSettings,
private val timeStamper: TimeStamper
private val timeStamper: TimeStamper,
private val exposureDetectionTracker: ExposureDetectionTracker,
private val keyPackageSyncSettings: KeyPackageSyncSettings
) : CWAViewModel(
dispatcherProvider = dispatcherProvider
) {
Expand All @@ -78,7 +75,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
Timber.d("Example arg: %s", exampleArg)
}

val riskLevelResetEvent = SingleLiveEvent<Unit>()
val dataResetEvent = SingleLiveEvent<String>()
val shareFileEvent = SingleLiveEvent<File>()

val showRiskStatusCard = SubmissionRepository.deviceUIStateFlow.map {
Expand Down Expand Up @@ -151,8 +148,8 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(

val additionalRiskCalcInfo = combine(
riskLevelStorage.riskLevelResults,
LocalData.lastTimeDiagnosisKeysFromServerFetchFlow()
) { riskLevelResults, lastTimeDiagnosisKeysFromServerFetch ->
exposureDetectionTracker.latestSubmission()
) { riskLevelResults, latestSubmission ->

val (latestCalc, latestSuccessfulCalc) = riskLevelResults.tryLatestResultsWithDefaults()

Expand All @@ -162,7 +159,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
riskLevelLastSuccessfulCalculated = latestSuccessfulCalc.riskState,
matchedKeyCount = latestCalc.matchedKeyCount,
daysSinceLastExposure = latestCalc.daysWithEncounters,
lastTimeDiagnosisKeysFromServerFetch = lastTimeDiagnosisKeysFromServerFetch
lastKeySubmission = latestSubmission?.startedAt
)
}.asLiveData()

Expand All @@ -172,13 +169,13 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
riskLevelLastSuccessfulCalculated: RiskState,
matchedKeyCount: Int,
daysSinceLastExposure: Int,
lastTimeDiagnosisKeysFromServerFetch: Date?
lastKeySubmission: Instant?
): String = StringBuilder()
.appendLine("Risk Level: $riskLevel")
.appendLine("Last successful Risk Level: $riskLevelLastSuccessfulCalculated")
.appendLine("Matched key count: $matchedKeyCount")
.appendLine("Days since last Exposure: $daysSinceLastExposure days")
.appendLine("Last Time Server Fetch: ${lastTimeDiagnosisKeysFromServerFetch?.time?.let { Instant.ofEpochMilli(it) }}")
.appendLine("Last key submission: $lastKeySubmission")
.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 $lastTimeRiskLevelCalculation")
Expand Down Expand Up @@ -210,24 +207,8 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
fun resetRiskLevel() {
Timber.d("Resetting risk level")
launch {
withContext(Dispatchers.IO) {
try {
// Preference reset
SecurityHelper.resetSharedPrefs()
// Database Reset
AppDatabase.reset(context)
// Export File Reset
keyCacheRepository.clear()

riskLevelStorage.clear()

LocalData.lastTimeDiagnosisKeysFromServerFetch(null)
} catch (e: Exception) {
e.report(ExceptionCategory.INTERNAL)
}
}
taskController.submit(DefaultTaskRequest(RiskLevelTask::class))
riskLevelResetEvent.postValue(Unit)
riskLevelStorage.clear()
dataResetEvent.postValue("Risk level calculation related data reset.")
}
}

Expand Down Expand Up @@ -257,7 +238,13 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(

fun clearKeyCache() {
Timber.d("Clearing key cache")
launch { keyCacheRepository.clear() }
launch {
keyCacheRepository.clear()
keyPackageSyncSettings.clear()
exposureDetectionTracker.clear()

dataResetEvent.postValue("Download & Submission related data reset.")
}
}

fun selectFakeExposureWindowMode(newMode: TestSettings.FakeExposureWindowTypes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<LinearLayout
android:id="@+id/environment_container"
style="@style/card"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:id="@+id/fake_windows_title"
Expand All @@ -50,11 +50,11 @@
android:text="Fake exposure windows" />

<TextView
android:layout_width="match_parent"
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:text="Takes effect the next time `ExposureNotificationClient.exposureWindows` is called, i.e. on risk level calculation."
android:layout_height="wrap_content" />
android:text="Takes effect the next time `ExposureNotificationClient.exposureWindows` is called, i.e. on risk level calculation." />

<RadioGroup
android:id="@+id/fake_windows_toggle_group"
Expand Down Expand Up @@ -83,36 +83,62 @@
</FrameLayout>

<Button
android:id="@+id/button_retrieve_diagnosis_keys"
android:id="@+id/button_calculate_risk_level"
style="@style/buttonPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:text="Retrieve Diagnosis Keys" />
android:text="Calculate Risk Level" />
<TextView
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:text="Start the task that gets the latest exposure windows and calculates a current risk state." />

<Button
android:id="@+id/button_calculate_risk_level"
android:id="@+id/button_reset_risk_level"
style="@style/buttonPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:text="Calculate Risk Level" />
android:text="Reset Risk Level" />

<TextView
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:text="Delete the all stored calculated risk level results." />

<Button
android:id="@+id/button_reset_risk_level"
android:id="@+id/button_retrieve_diagnosis_keys"
style="@style/buttonPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:text="Reset Risk Level" />
android:text="Download Diagnosis Keys" />

<TextView
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:text="Start the task syncs the local diagnosis key cache with the server and submits them to the exposure notification framework for detection (if constraints allow). " />

<Button
android:id="@+id/button_clear_diagnosis_key_cache"
style="@style/buttonPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:text="Clear Diagnosis-Key cache" />
android:text="Reset Diagnosis-Keys" />
<TextView
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:text="Restore download task conditions to initial state. Remove cached keys, delete last download logs, reset tracked exposure detections. " />

<TextView
android:id="@+id/label_aggregated_risk_result_title"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import de.rki.coronawarnapp.nearby.ENFClient
import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
import de.rki.coronawarnapp.nearby.modules.detectiontracker.TrackedExposureDetection
import de.rki.coronawarnapp.risk.RollbackItem
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.task.Task
import de.rki.coronawarnapp.task.TaskCancellationException
import de.rki.coronawarnapp.task.TaskFactory
Expand Down Expand Up @@ -109,12 +108,6 @@ class DownloadDiagnosisKeysTask @Inject constructor(
)
Timber.tag(TAG).d("Diagnosis Keys provided (success=%s)", isSubmissionSuccessful)

// EXPOSUREAPP-3878 write timestamp immediately after submission,
// so that progress observers can rely on a clean app state
if (isSubmissionSuccessful) {
saveTimestamp(currentDate, rollbackItems)
}

internalProgress.send(Progress.ApiSubmissionFinished)

return object : Task.Result {}
Expand Down Expand Up @@ -159,18 +152,6 @@ class DownloadDiagnosisKeysTask @Inject constructor(
}
}

private fun saveTimestamp(
currentDate: Date,
rollbackItems: MutableList<RollbackItem>
) {
val lastFetchDateForRollback = LocalData.lastTimeDiagnosisKeysFromServerFetch()
rollbackItems.add {
LocalData.lastTimeDiagnosisKeysFromServerFetch(lastFetchDateForRollback)
}
Timber.tag(TAG).d("dateUpdate(currentDate=%s)", currentDate)
LocalData.lastTimeDiagnosisKeysFromServerFetch(currentDate)
}

private fun rollback(rollbackItems: MutableList<RollbackItem>) {
try {
Timber.tag(TAG).d("Initiate Rollback")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.rki.coronawarnapp.nearby.modules.detectiontracker

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

suspend fun ExposureDetectionTracker.lastSubmission(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not mind moving this into the same file as the interface itself. Was shortly confused about the location of lastSubmission when reviewing this PR

onlyFinished: Boolean = true
): TrackedExposureDetection? = calculations
.first().values
.filter { it.isSuccessful || !onlyFinished }
.maxByOrNull { it.startedAt }

fun ExposureDetectionTracker.latestSubmission(
onlySuccessful: Boolean = true
): Flow<TrackedExposureDetection?> = calculations
.map { entries ->
entries.values.filter { it.isSuccessful || !onlySuccessful }
}
.map { detections ->
detections.maxByOrNull { it.startedAt }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import android.content.SharedPreferences
import androidx.core.content.edit
import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.util.preferences.createFlowPreference
import de.rki.coronawarnapp.util.security.SecurityHelper.globalEncryptedSharedPreferencesInstance
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import java.util.Date
import timber.log.Timber

/**
* LocalData is responsible for all access to the shared preferences. Each preference is accessible
Expand Down Expand Up @@ -306,28 +304,6 @@ object LocalData {
.edit(commit = true) { putBoolean(PREFERENCE_HAS_RISK_STATUS_LOWERED, value) }
.also { isUserToBeNotifiedOfLoweredRiskLevelFlowInternal.value = value }

/****************************************************
* SERVER FETCH DATA
****************************************************/

private val dateMapperForFetchTime: (Long) -> Date? = {
if (it != 0L) Date(it) else null
}

private val lastTimeDiagnosisKeysFetchedFlowPref by lazy {
getSharedPreferenceInstance()
.createFlowPreference<Long>(key = "preference_timestamp_diagnosis_keys_fetch", 0L)
}

fun lastTimeDiagnosisKeysFromServerFetchFlow() = lastTimeDiagnosisKeysFetchedFlowPref.flow
.map { dateMapperForFetchTime(it) }

fun lastTimeDiagnosisKeysFromServerFetch() =
dateMapperForFetchTime(lastTimeDiagnosisKeysFetchedFlowPref.value)

fun lastTimeDiagnosisKeysFromServerFetch(value: Date?) =
lastTimeDiagnosisKeysFetchedFlowPref.update { value?.time ?: 0L }

/****************************************************
* SETTINGS DATA
****************************************************/
Expand Down Expand Up @@ -583,6 +559,6 @@ object LocalData {
}

fun clear() {
lastTimeDiagnosisKeysFetchedFlowPref.update { 0L }
Timber.w("LocalData.clear()")
}
}
Loading