Skip to content
Snippets Groups Projects
Unverified Commit 3c44efc8 authored by Chilja Gossow's avatar Chilja Gossow Committed by GitHub
Browse files

Abort test result available notification scheduling when viewed (EXPOSUREAPP-4516) #2105

* do not schedule reminders when test result has been viewed

* clean up

* renaming to make clear which notification is meant

* refactor worker

* rename

* DI issues

* DI test code

* add else branch
test code

* test code

* refactoring

* comment
parent 5fd9d9e0
No related branches found
No related tags found
No related merge requests found
Showing
with 109 additions and 75 deletions
package de.rki.coronawarnapp.ui.submission.testresult.pending
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.navigation.NavDirections
import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.exception.http.CwaWebException
import de.rki.coronawarnapp.notification.TestResultNotificationService
import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
import de.rki.coronawarnapp.submission.SubmissionRepository
import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
import de.rki.coronawarnapp.util.DeviceUIState
......@@ -26,7 +27,7 @@ import timber.log.Timber
class SubmissionTestResultPendingViewModel @AssistedInject constructor(
dispatcherProvider: DispatcherProvider,
private val testResultNotificationService: TestResultNotificationService,
private val shareTestResultNotificationService: ShareTestResultNotificationService,
private val submissionRepository: SubmissionRepository
) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
......@@ -59,8 +60,7 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
testResultReceivedDate = resultDate
)
}
val testState = testResultFlow
val testState: LiveData<TestResultUIState> = testResultFlow
.onEach { testResultUIState ->
testResultUIState.deviceUiState.withSuccess { deviceState ->
when (deviceState) {
......@@ -100,7 +100,7 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
it == DeviceUIState.PAIRED_POSITIVE || it == DeviceUIState.PAIRED_POSITIVE_TELETAN
}
}
.also { testResultNotificationService.schedulePositiveTestResultReminder() }
.also { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() }
}
fun deregisterTestFromDevice() {
......
......@@ -3,6 +3,7 @@ package de.rki.coronawarnapp.ui.submission.testresult.positive
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
import de.rki.coronawarnapp.submission.SubmissionRepository
import de.rki.coronawarnapp.submission.auto.AutoSubmission
import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
......@@ -18,6 +19,7 @@ import timber.log.Timber
class SubmissionTestResultConsentGivenViewModel @AssistedInject constructor(
private val submissionRepository: SubmissionRepository,
private val autoSubmission: AutoSubmission,
private val testResultAvailableNotificationService: TestResultAvailableNotificationService,
dispatcherProvider: DispatcherProvider
) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
......@@ -40,6 +42,7 @@ class SubmissionTestResultConsentGivenViewModel @AssistedInject constructor(
fun onTestOpened() {
submissionRepository.setViewedTestResult()
testResultAvailableNotificationService.cancelTestResultAvailableNotification()
}
fun onContinuePressed() {
......
......@@ -3,6 +3,7 @@ package de.rki.coronawarnapp.ui.submission.testresult.positive
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
import de.rki.coronawarnapp.submission.SubmissionRepository
import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
import de.rki.coronawarnapp.util.flow.combine
......@@ -11,7 +12,8 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import kotlinx.coroutines.Dispatchers
class SubmissionTestResultNoConsentViewModel @AssistedInject constructor(
val submissionRepository: SubmissionRepository
private val submissionRepository: SubmissionRepository,
private val testResultAvailableNotificationService: TestResultAvailableNotificationService
) : CWAViewModel() {
val uiState: LiveData<TestResultUIState> = combine(
......@@ -27,6 +29,7 @@ class SubmissionTestResultNoConsentViewModel @AssistedInject constructor(
fun onTestOpened() {
submissionRepository.setViewedTestResult()
testResultAvailableNotificationService.cancelTestResultAvailableNotification()
}
@AssistedInject.Factory
......
......@@ -84,7 +84,7 @@ object BackgroundWorkScheduler {
notificationBody.append("[DIAGNOSIS_KEY_BACKGROUND_PERIODIC_WORK] ")
}
if (!isWorkActive(WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER.tag) &&
LocalData.registrationToken() != null && !LocalData.isTestResultNotificationSent()
LocalData.registrationToken() != null && !LocalData.isTestResultAvailableNotificationSent()
) {
WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER.start()
LocalData.initialPollingForTestResultTimeStamp(System.currentTimeMillis())
......
......@@ -8,9 +8,10 @@ import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
import de.rki.coronawarnapp.notification.NotificationConstants
import de.rki.coronawarnapp.notification.NotificationHelper
import de.rki.coronawarnapp.notification.TestResultAvailableNotification
import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
import de.rki.coronawarnapp.service.submission.SubmissionService
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.submission.SubmissionSettings
import de.rki.coronawarnapp.util.TimeAndDateExtensions
import de.rki.coronawarnapp.util.formatter.TestResult
import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory
......@@ -25,16 +26,17 @@ import timber.log.Timber
class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
@Assisted val context: Context,
@Assisted workerParams: WorkerParameters,
private val submissionService: SubmissionService,
private val testResultAvailableNotification: TestResultAvailableNotification,
private val notificationHelper: NotificationHelper
private val testResultAvailableNotificationService: TestResultAvailableNotificationService,
private val notificationHelper: NotificationHelper,
private val submissionSettings: SubmissionSettings,
private val submissionService: SubmissionService
) : CoroutineWorker(context, workerParams) {
/**
* If background job is running for less than 21 days, testResult is checked.
* If the job is running for more than 21 days, the job will be stopped
*
* @see LocalData.isTestResultNotificationSent
* @see LocalData.isTestResultAvailableNotificationSent
* @see LocalData.initialPollingForTestResultTimeStamp
*/
override suspend fun doWork(): Result {
......@@ -50,19 +52,27 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
}
var result = Result.success()
try {
if (TimeAndDateExtensions.calculateDays(
LocalData.initialPollingForTestResultTimeStamp(),
System.currentTimeMillis()
) < BackgroundConstants.POLLING_VALIDITY_MAX_DAYS
) {
Timber.tag(TAG).d(" $id maximum days not exceeded")
if (abortConditionsMet()) {
Timber.tag(TAG).d(" $id Stopping worker.")
stopWorker()
} else {
Timber.tag(TAG).d(" $id Running worker.")
val registrationToken = LocalData.registrationToken() ?: throw NoRegistrationTokenSetException()
val testResult = submissionService.asyncRequestTestResult(registrationToken)
initiateNotification(testResult)
Timber.tag(TAG).d(" $id Test Result Notification Initiated")
} else {
stopWorker()
Timber.tag(TAG).d(" $id worker stopped")
Timber.tag(TAG).d("$id: Test Result retrieved is $testResult")
if (testResult == TestResult.NEGATIVE ||
testResult == TestResult.POSITIVE ||
testResult == TestResult.INVALID
) {
sendTestResultAvailableNotification(testResult)
cancelRiskLevelScoreNotification()
Timber.tag(TAG)
.d("$id: Test Result available - notification sent & risk level notification canceled")
stopWorker()
}
}
} catch (e: Exception) {
result = Result.retry()
......@@ -73,35 +83,38 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
return result
}
/**
* Notification Initiation
*
* If the returned Test Result is Negative, Positive or Invalid
* The Background polling will be stopped
* and a notification is shown, but only if the App is not in foreground
*
* @see LocalData.isTestResultNotificationSent
* @see LocalData.initialPollingForTestResultTimeStamp
* @see TestResult
*/
private suspend fun initiateNotification(testResult: TestResult) {
if (LocalData.isTestResultNotificationSent() || LocalData.submissionWasSuccessful()) {
Timber.tag(TAG).d("$id: Notification already sent or there was a successful submission")
return
private fun abortConditionsMet(): Boolean {
if (LocalData.isTestResultAvailableNotificationSent()) {
Timber.tag(TAG).d("$id: Notification already sent.")
return true
}
if (submissionSettings.hasViewedTestResult.value) {
Timber.tag(TAG).d("$id: Test result has already been viewed.")
return true
}
Timber.tag(TAG).d("$id: Test Result retried is $testResult")
if (testResult == TestResult.NEGATIVE || testResult == TestResult.POSITIVE ||
testResult == TestResult.INVALID
if (TimeAndDateExtensions.calculateDays(
LocalData.initialPollingForTestResultTimeStamp(),
System.currentTimeMillis()
) >= BackgroundConstants.POLLING_VALIDITY_MAX_DAYS
) {
testResultAvailableNotification.showTestResultNotification(testResult)
Timber.tag(TAG)
.d(" $id Maximum of ${BackgroundConstants.POLLING_VALIDITY_MAX_DAYS} days for polling exceeded.")
return true
}
notificationHelper.cancelCurrentNotification(
NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID)
return false
}
Timber.tag(TAG).d("$id: Test Result available - notification issued & risk level notification canceled")
LocalData.isTestResultNotificationSent(true)
stopWorker()
}
private suspend fun sendTestResultAvailableNotification(testResult: TestResult) {
testResultAvailableNotificationService.showTestResultAvailableNotification(testResult)
LocalData.isTestResultAvailableNotificationSent(true)
}
private fun cancelRiskLevelScoreNotification() {
notificationHelper.cancelCurrentNotification(
NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
)
}
/**
......
......@@ -3,7 +3,7 @@ package de.rki.coronawarnapp.main.home
import android.content.Context
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.main.CWASettings
import de.rki.coronawarnapp.notification.TestResultNotificationService
import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
import de.rki.coronawarnapp.storage.TracingRepository
import de.rki.coronawarnapp.submission.SubmissionRepository
import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone
......@@ -51,7 +51,7 @@ class HomeFragmentViewModelTest : BaseTest() {
@MockK lateinit var tracingStateProviderFactory: TracingStateProvider.Factory
@MockK lateinit var submissionStateProvider: SubmissionStateProvider
@MockK lateinit var tracingRepository: TracingRepository
@MockK lateinit var testResultNotificationService: TestResultNotificationService
@MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService
@MockK lateinit var submissionRepository: SubmissionRepository
@MockK lateinit var cwaSettings: CWASettings
@MockK lateinit var appConfigProvider: AppConfigProvider
......@@ -82,7 +82,7 @@ class HomeFragmentViewModelTest : BaseTest() {
errorResetTool = errorResetTool,
tracingStatus = generalTracingStatus,
tracingRepository = tracingRepository,
testResultNotificationService = testResultNotificationService,
shareTestResultNotificationService = shareTestResultNotificationService,
submissionRepository = submissionRepository,
submissionStateProvider = submissionStateProvider,
tracingStateProviderFactory = tracingStateProviderFactory,
......@@ -141,12 +141,12 @@ class HomeFragmentViewModelTest : BaseTest() {
every { submissionRepository.deviceUIStateFlow } returns flowOf(
NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE)
)
every { testResultNotificationService.schedulePositiveTestResultReminder() } returns Unit
every { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } returns Unit
runBlocking {
createInstance().apply {
observeTestResultToSchedulePositiveTestResultReminder()
verify { testResultNotificationService.schedulePositiveTestResultReminder() }
verify { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() }
}
}
}
......@@ -156,12 +156,12 @@ class HomeFragmentViewModelTest : BaseTest() {
every { submissionRepository.deviceUIStateFlow } returns flowOf(
NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE_TELETAN)
)
every { testResultNotificationService.schedulePositiveTestResultReminder() } returns Unit
every { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } returns Unit
runBlocking {
createInstance().apply {
observeTestResultToSchedulePositiveTestResultReminder()
verify { testResultNotificationService.schedulePositiveTestResultReminder() }
verify { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() }
}
}
}
......
......@@ -26,7 +26,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import javax.inject.Provider
class TestResultAvailableNotificationTest {
class TestResultAvailableNotificationServiceTest {
@MockK(relaxed = true) lateinit var context: Context
@MockK lateinit var foregroundState: ForegroundState
......@@ -53,7 +53,7 @@ class TestResultAvailableNotificationTest {
clearAllMocks()
}
fun createInstance() = TestResultAvailableNotification(
fun createInstance() = TestResultAvailableNotificationService(
context = context,
foregroundState = foregroundState,
navDeepLinkBuilderProvider = navDeepLinkBuilderProvider,
......@@ -76,7 +76,7 @@ class TestResultAvailableNotificationTest {
fun `test notification in foreground`() = runBlockingTest {
coEvery { foregroundState.isInForeground } returns flow { emit(true) }
createInstance().showTestResultNotification(TestResult.POSITIVE)
createInstance().showTestResultAvailableNotification(TestResult.POSITIVE)
verify(exactly = 0) { navDeepLinkBuilderProvider.get() }
}
......@@ -95,7 +95,7 @@ class TestResultAvailableNotificationTest {
val instance = createInstance()
instance.showTestResultNotification(TestResult.POSITIVE)
instance.showTestResultAvailableNotification(TestResult.POSITIVE)
verifyOrder {
navDeepLinkBuilderProvider.get()
......
......@@ -99,7 +99,7 @@ class SubmissionRepositoryTest {
every { LocalData.initialPollingForTestResultTimeStamp(any()) } just Runs
every { LocalData.initialTestResultReceivedTimestamp(any()) } just Runs
every { LocalData.isAllowedToSubmitDiagnosisKeys(any()) } just Runs
every { LocalData.isTestResultNotificationSent(any()) } just Runs
every { LocalData.isTestResultAvailableNotificationSent(any()) } just Runs
submissionRepository.removeTestFromDevice()
......@@ -109,7 +109,7 @@ class SubmissionRepositoryTest {
LocalData.initialPollingForTestResultTimeStamp(0L)
LocalData.initialTestResultReceivedTimestamp(0L)
LocalData.isAllowedToSubmitDiagnosisKeys(false)
LocalData.isTestResultNotificationSent(false)
LocalData.isTestResultAvailableNotificationSent(false)
}
}
......
......@@ -4,8 +4,8 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.appconfig.ConfigData
import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
import de.rki.coronawarnapp.notification.TestResultAvailableNotification
import de.rki.coronawarnapp.notification.TestResultNotificationService
import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
import de.rki.coronawarnapp.playbook.Playbook
import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass
import de.rki.coronawarnapp.storage.LocalData
......@@ -48,8 +48,8 @@ class SubmissionTaskTest : BaseTest() {
@MockK lateinit var tekHistoryCalculations: ExposureKeyHistoryCalculations
@MockK lateinit var tekHistoryStorage: TEKHistoryStorage
@MockK lateinit var submissionSettings: SubmissionSettings
@MockK lateinit var testResultNotificationService: TestResultNotificationService
@MockK lateinit var testResultAvailableNotification: TestResultAvailableNotification
@MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService
@MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService
@MockK lateinit var autoSubmission: AutoSubmission
@MockK lateinit var tekBatch: TEKHistoryStorage.TEKBatch
......@@ -100,8 +100,8 @@ class SubmissionTaskTest : BaseTest() {
coEvery { playbook.submit(any()) } just Runs
every { testResultNotificationService.cancelPositiveTestResultNotification() } just Runs
every { testResultAvailableNotification.cancelTestResultNotification() } just Runs
every { shareTestResultNotificationService.cancelSharePositiveTestResultNotification() } just Runs
every { testResultAvailableNotificationService.cancelTestResultAvailableNotification() } just Runs
every { autoSubmission.updateMode(any()) } just Runs
......@@ -114,10 +114,10 @@ class SubmissionTaskTest : BaseTest() {
tekHistoryCalculations = tekHistoryCalculations,
tekHistoryStorage = tekHistoryStorage,
submissionSettings = submissionSettings,
testResultNotificationService = testResultNotificationService,
shareTestResultNotificationService = shareTestResultNotificationService,
timeStamper = timeStamper,
autoSubmission = autoSubmission,
testResultAvailableNotification = testResultAvailableNotification
testResultAvailableNotificationService = testResultAvailableNotificationService
)
@Test
......@@ -155,8 +155,8 @@ class SubmissionTaskTest : BaseTest() {
BackgroundWorkScheduler.stopWorkScheduler()
LocalData.numberOfSuccessfulSubmissions(1)
testResultNotificationService.cancelPositiveTestResultNotification()
testResultAvailableNotification.cancelTestResultNotification()
shareTestResultNotificationService.cancelSharePositiveTestResultNotification()
testResultAvailableNotificationService.cancelTestResultAvailableNotification()
}
}
......@@ -205,7 +205,7 @@ class SubmissionTaskTest : BaseTest() {
coVerify(exactly = 0) {
tekHistoryStorage.clear()
settingSymptomsPreference.update(any())
testResultNotificationService.cancelPositiveTestResultNotification()
shareTestResultNotificationService.cancelSharePositiveTestResultNotification()
autoSubmission.updateMode(any())
}
submissionSettings.symptoms.value shouldBe userSymptoms
......
package de.rki.coronawarnapp.ui.submission.testresult
import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
import de.rki.coronawarnapp.submission.SubmissionRepository
import de.rki.coronawarnapp.submission.auto.AutoSubmission
import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultConsentGivenViewModel
......@@ -18,6 +19,7 @@ import testhelpers.extensions.InstantExecutorExtension
class SubmissionTestResultConsentGivenViewModelTest : BaseTest() {
@MockK lateinit var submissionRepository: SubmissionRepository
@MockK lateinit var autoSubmission: AutoSubmission
@MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService
lateinit var viewModel: SubmissionTestResultConsentGivenViewModel
@BeforeEach
......@@ -28,7 +30,8 @@ class SubmissionTestResultConsentGivenViewModelTest : BaseTest() {
private fun createViewModel() = SubmissionTestResultConsentGivenViewModel(
submissionRepository = submissionRepository,
dispatcherProvider = TestDispatcherProvider,
autoSubmission = autoSubmission
autoSubmission = autoSubmission,
testResultAvailableNotificationService = testResultAvailableNotificationService
)
@Test
......
package de.rki.coronawarnapp.util.worker
import android.content.Context
import androidx.work.ListenableWorker
import com.google.gson.Gson
import dagger.Component
import dagger.Module
import dagger.Provides
......@@ -8,11 +10,13 @@ import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
import de.rki.coronawarnapp.deadman.DeadmanNotificationSender
import de.rki.coronawarnapp.nearby.ENFClient
import de.rki.coronawarnapp.notification.NotificationHelper
import de.rki.coronawarnapp.notification.TestResultAvailableNotification
import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
import de.rki.coronawarnapp.playbook.Playbook
import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
import de.rki.coronawarnapp.task.TaskController
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.di.AssistedInjectModule
import de.rki.coronawarnapp.util.serialization.BaseGson
import io.github.classgraph.ClassGraph
import io.kotest.matchers.collections.shouldContainAll
import io.mockk.mockk
......@@ -100,8 +104,16 @@ class MockProvider {
fun exposureSummaryRepository(): RiskLevelStorage = mockk()
@Provides
fun testResultAvailableNotification(): TestResultAvailableNotification = mockk()
fun testResultAvailableNotification(): TestResultAvailableNotificationService = mockk()
@Provides
fun notificationHelper(): NotificationHelper = mockk()
@Provides
@AppContext
fun context(): Context = mockk()
@Provides
@BaseGson
fun baseGson(): Gson = mockk()
}
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