diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt index a0362781c44750b755b88ca6f51228a89fd8d598..61922e75255197b0c1ac393c21c0d9fc44b55765 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.ui.onboarding import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.TracingPermissionHelper import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository @@ -33,6 +34,7 @@ class OnboardingTracingFragmentTest : BaseUITest() { @MockK lateinit var interopRepo: InteroperabilityRepository @MockK lateinit var factory: TracingPermissionHelper.Factory @MockK lateinit var tracingSettings: TracingSettings + @MockK lateinit var enfClient: ENFClient @Rule @JvmField @@ -50,7 +52,8 @@ class OnboardingTracingFragmentTest : BaseUITest() { interoperabilityRepository = interopRepo, tracingPermissionHelperFactory = factory, dispatcherProvider = TestDispatcherProvider(), - tracingSettings = tracingSettings + tracingSettings = tracingSettings, + enfClient = enfClient, ) ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt deleted file mode 100644 index 7229fda0a8ad3809edf317bfa0eaaf3f401daf06..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package de.rki.coronawarnapp.nearby - -import de.rki.coronawarnapp.util.di.AppInjector -import kotlinx.coroutines.flow.first -import timber.log.Timber -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -/** - * Wrapper class for the Exposure Notification Client in the com.google.android.gms.nearby.Nearby - * implementing all Exposure Notification related APIs - */ -object InternalExposureNotificationClient { - - // reference to the client from the Google framework with the given application context - private val enfClient by lazy { - AppInjector.component.enfClient - } - - /** - * Disables broadcasting and scanning. You can call this directly, and it is also called when - * users uninstall the app. When it’s called as part of the uninstallation process, the - * database and keys are deleted from the device. - * - * @return - */ - suspend fun asyncStop() = suspendCoroutine<Unit> { cont -> - enfClient.setTracing( - false, - onSuccess = { cont.resume(Unit) }, - onError = { cont.resumeWithException(it) }, - onPermissionRequired = { Timber.e("Permission was required to disable tracing?") } - ) - } - - /** - * Indicates if exposure notifications are running - * - * @return - */ - suspend fun asyncIsEnabled(): Boolean = enfClient.isTracingEnabled.first() -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tracing/TracingStatus.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tracing/TracingStatus.kt index d98223148d89af4d909c2a6723c820a7d7713e54..47ae9c4df79425937e50c7890b83a2ad7c8d65a1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tracing/TracingStatus.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/tracing/TracingStatus.kt @@ -2,6 +2,11 @@ package de.rki.coronawarnapp.nearby.modules.tracing import com.google.android.gms.common.api.Status import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import timber.log.Timber +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine interface TracingStatus { val isTracingEnabled: Flow<Boolean> @@ -13,3 +18,22 @@ interface TracingStatus { onPermissionRequired: (Status) -> Unit ) } + +/** + * Returns true if tracing was disabled. + */ +suspend fun TracingStatus.disableTracingIfEnabled(): Boolean { + if (!isTracingEnabled.first()) { + Timber.d("Tracing was already disabled.") + return false + } + + return suspendCoroutine { cont -> + setTracing( + enable = false, + onSuccess = { cont.resume(true) }, + onError = { cont.resumeWithException(it) }, + onPermissionRequired = { Timber.e("Permission was required to disable tracing?") } + ) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt index 7a80372f1eb61ddb8dc1bd4c175bf4efae1d9c53..7ca4411fa7d03804e156cae6a64486abb7f9075c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt @@ -3,7 +3,6 @@ package de.rki.coronawarnapp.storage import android.annotation.SuppressLint import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask import de.rki.coronawarnapp.nearby.ENFClient -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.lastSubmission import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingRiskWorkScheduler @@ -34,8 +33,6 @@ import javax.inject.Singleton /** * The Tracing Repository refreshes and triggers all tracing relevant data. Some functions get their * data directly from the Exposure Notification, others consume the shared preferences. - * - * @see InternalExposureNotificationClient */ @Singleton class TracingRepository @Inject constructor( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragment.kt index e6c4f67d8b7e00f2d1c690da682d74cf9ec0b6fa..d4fabbb857abca5d5c780a85bc4228a8c9115449 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragment.kt @@ -8,7 +8,6 @@ import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder import de.rki.coronawarnapp.databinding.FragmentSettingsTracingBinding -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.tracing.ui.TracingConsentDialog import de.rki.coronawarnapp.tracing.ui.settings.SettingsTracingFragmentViewModel.Event import de.rki.coronawarnapp.util.DialogHelper @@ -24,8 +23,6 @@ import javax.inject.Inject /** * The user can start/stop tracing and is informed about tracing. - * - * @see InternalExposureNotificationClient */ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), AutoInject { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt index 3ec86a0a71c21b36e1b2abb6f63d27f99f56b2ef..bb83ecf881dc231a987b01d19f75972f91d6f351 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt @@ -11,8 +11,9 @@ import dagger.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.installTime.InstallTimeProvider -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.TracingPermissionHelper +import de.rki.coronawarnapp.nearby.modules.tracing.disableTracingIfEnabled import de.rki.coronawarnapp.risk.execution.ExposureWindowRiskWorkScheduler import de.rki.coronawarnapp.tracing.GeneralTracingStatus import de.rki.coronawarnapp.tracing.ui.details.items.periodlogged.PeriodLoggedBox @@ -35,7 +36,8 @@ class SettingsTracingFragmentViewModel @AssistedInject constructor( installTimeProvider: InstallTimeProvider, private val backgroundStatus: BackgroundModeStatus, tracingPermissionHelperFactory: TracingPermissionHelper.Factory, - private val exposureWindowRiskWorkScheduler: ExposureWindowRiskWorkScheduler + private val exposureWindowRiskWorkScheduler: ExposureWindowRiskWorkScheduler, + private val enfClient: ENFClient, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val loggingPeriod: LiveData<PeriodLoggedBox.Item> = @@ -120,8 +122,7 @@ class SettingsTracingFragmentViewModel @AssistedInject constructor( isTracingSwitchChecked.postValue(false) launch { try { - if (InternalExposureNotificationClient.asyncIsEnabled()) { - InternalExposureNotificationClient.asyncStop() + if (enfClient.disableTracingIfEnabled()) { exposureWindowRiskWorkScheduler.setPeriodicRiskCalculation(enabled = false) } } catch (exception: Exception) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt index 94f0dadf744b212591c86373f78601061d79f012..d629c1267c72337575951e99d478175a7e4ef6ea 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentViewModel.kt @@ -7,8 +7,9 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient +import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.TracingPermissionHelper +import de.rki.coronawarnapp.nearby.modules.tracing.disableTracingIfEnabled import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -22,6 +23,7 @@ class OnboardingTracingFragmentViewModel @AssistedInject constructor( tracingPermissionHelperFactory: TracingPermissionHelper.Factory, dispatcherProvider: DispatcherProvider, private val tracingSettings: TracingSettings, + private val enfClient: ENFClient, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val countryList = interoperabilityRepository.countryList @@ -63,8 +65,7 @@ class OnboardingTracingFragmentViewModel @AssistedInject constructor( fun resetTracing() { launch { try { - if (InternalExposureNotificationClient.asyncIsEnabled()) { - InternalExposureNotificationClient.asyncStop() + if (enfClient.disableTracingIfEnabled()) { tracingSettings.isConsentGiven = false } } catch (exception: Exception) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt index 9260aefa17d5e884a7986acd9ee4ea6a2b77bd13..0c3b5dbb542d11d19dd1ffcffafc4daefb6cb265 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt @@ -5,7 +5,8 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient +import de.rki.coronawarnapp.nearby.ENFClient +import de.rki.coronawarnapp.nearby.modules.tracing.disableTracingIfEnabled import de.rki.coronawarnapp.util.DataReset import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper @@ -17,6 +18,7 @@ class SettingsResetViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val dataReset: DataReset, private val shortcutsHelper: AppShortcutsHelper, + private val enfClient: ENFClient, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val clickEvent: SingleLiveEvent<SettingsEvents> = SingleLiveEvent() @@ -32,18 +34,9 @@ class SettingsResetViewModel @AssistedInject constructor( fun deleteAllAppContent() { launch { try { - // TODO Remove static access - val isTracingEnabled = InternalExposureNotificationClient.asyncIsEnabled() - // only stop tracing if it is currently enabled - if (isTracingEnabled) { - InternalExposureNotificationClient.asyncStop() - } + enfClient.disableTracingIfEnabled() } catch (apiException: ApiException) { - apiException.report( - ExceptionCategory.EXPOSURENOTIFICATION, - TAG, - null - ) + apiException.report(ExceptionCategory.EXPOSURENOTIFICATION, TAG, null) } dataReset.clearAllLocalData() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatusTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatusTest.kt index b8d6def71692b477eaceed3955e68d459d4a02e1..4c4b84d92987e7922f6f1a2cab308433012d5895 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatusTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/tracing/DefaultTracingStatusTest.kt @@ -9,12 +9,15 @@ import io.mockk.Called import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -129,4 +132,32 @@ class DefaultTracingStatusTest : BaseTest() { thrownError shouldBe ourError } + + @Test + fun `extension for disabling tracing if enabled`() { + val enabledFlow = MutableStateFlow(false) + val tracingStatus = mockk<TracingStatus>().apply { + every { isTracingEnabled } returns enabledFlow + every { setTracing(any(), any(), any(), any()) } answers { + val enabled = arg<Boolean>(0) + val onSuccess = arg<(Boolean) -> Unit>(1) + val onError = arg<(Throwable) -> Unit>(2) + val onPermissionRequired = arg<(Status) -> Unit>(3) + + onSuccess(false) + } + } + + runBlocking { + tracingStatus.disableTracingIfEnabled() + verify(exactly = 0) { tracingStatus.setTracing(any(), any(), any(), any()) } + } + + enabledFlow.value = true + + runBlocking { + tracingStatus.disableTracingIfEnabled() shouldBe true + verify(exactly = 1) { tracingStatus.setTracing(any(), any(), any(), any()) } + } + } }