diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragment.kt
index 8a4c1ede0918cf48b1d5985503339db6e51f9ada..8e13984fffe6bd26b5072d31283cf0fe9c70e7c3 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragment.kt
@@ -9,6 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentTestSubmissionBinding
 import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
+import de.rki.coronawarnapp.tracing.ui.TracingConsentDialog
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.lists.diffutil.update
 import de.rki.coronawarnapp.util.ui.observe2
@@ -56,10 +57,19 @@ class SubmissionTestFragment : Fragment(R.layout.fragment_test_submission), Auto
         }
 
         binding.apply {
-            tekStorageUpdate.setOnClickListener { vm.updateStorage(requireActivity()) }
+            tekStorageUpdate.setOnClickListener { vm.updateStorage() }
             tekStorageClear.setOnClickListener { vm.clearStorage() }
             tekStorageEmail.setOnClickListener { vm.emailTEKs() }
         }
+        vm.permissionRequestEvent.observe2(this) { permissionRequest ->
+            permissionRequest.invoke(requireActivity())
+        }
+        vm.showTracingConsentDialog.observe2(this) { consentResult ->
+            TracingConsentDialog(requireContext()).show(
+                onConsentGiven = { consentResult(true) },
+                onConsentDeclined = { consentResult(false) }
+            )
+        }
     }
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt
index 030989be8146dc9ba1e20e44ed22b75e93ee65d9..f57f6374466ad8b768ddd765c0a6846efb5e6047 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt
@@ -10,7 +10,6 @@ import com.squareup.inject.assisted.AssistedInject
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage
 import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater
-import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater.UpdateResult
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.serialization.BaseGson
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
@@ -25,7 +24,7 @@ import java.util.UUID
 class SubmissionTestFragmentViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     private val tekHistoryStorage: TEKHistoryStorage,
-    private val tekHistoryUpdater: TEKHistoryUpdater,
+    tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory,
     @BaseGson baseGson: Gson
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
@@ -33,12 +32,37 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor(
         setPrettyPrinting()
     }.create()
 
+    private val tekHistoryUpdater = tekHistoryUpdaterFactory.create(object : TEKHistoryUpdater.Callback {
+        override fun onTEKAvailable(teks: List<TemporaryExposureKey>) {
+            Timber.d("TEKs are available: %s", teks)
+        }
+
+        override fun onTEKPermissionDeclined() {
+            Timber.d("Permission were declined.")
+        }
+
+        override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) {
+            showTracingConsentDialog.postValue(onConsentResult)
+        }
+
+        override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) {
+            permissionRequestEvent.postValue(permissionRequest)
+        }
+
+        override fun onError(error: Throwable) {
+            errorEvents.postValue(error)
+        }
+    })
+
     val errorEvents = SingleLiveEvent<Throwable>()
     private val internalToken = MutableStateFlow(LocalData.registrationToken())
     val currentTestId = internalToken.asLiveData()
 
     val shareTEKsEvent = SingleLiveEvent<TEKExport>()
 
+    val permissionRequestEvent = SingleLiveEvent<(Activity) -> Unit>()
+    val showTracingConsentDialog = SingleLiveEvent<(Boolean) -> Unit>()
+
     val tekHistory: LiveData<List<TEKHistoryItem>> = tekHistoryStorage.tekData
         .map { items ->
             items.flatMap { batch ->
@@ -55,22 +79,6 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor(
         .map { historyItems -> historyItems.sortedBy { it.obtainedAt } }
         .asLiveData(context = dispatcherProvider.Default)
 
-    init {
-        tekHistoryUpdater.callback = object : TEKHistoryUpdater.Callback {
-            override fun onTEKAvailable(teks: List<TemporaryExposureKey>) {
-                Timber.d("TEKs are available: %s", teks)
-            }
-
-            override fun onPermissionDeclined() {
-                Timber.d("Permission were declined.")
-            }
-
-            override fun onError(error: Throwable) {
-                errorEvents.postValue(error)
-            }
-        }
-    }
-
     fun scrambleRegistrationToken() {
         LocalData.registrationToken(UUID.randomUUID().toString())
         internalToken.value = LocalData.registrationToken()
@@ -81,10 +89,8 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor(
         internalToken.value = LocalData.registrationToken()
     }
 
-    fun updateStorage(activity: Activity) {
-        tekHistoryUpdater.updateTEKHistoryOrRequestPermission { permissionRequest ->
-            permissionRequest.invoke(activity)
-        }
+    fun updateStorage() {
+        tekHistoryUpdater.updateTEKHistoryOrRequestPermission()
     }
 
     fun clearStorage() {
@@ -105,21 +111,9 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor(
     }
 
     fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
-        val result = tekHistoryUpdater.handleActivityResult(requestCode, resultCode, data)
-        Timber.d("tekHistoryUpdater.handleActivityResult(): %s", result)
-
-        if (result == UpdateResult.PERMISSION_AVAILABLE) {
-            launch {
-                try {
-                    tekHistoryUpdater.updateHistoryOrThrow()
-                } catch (e: Exception) {
-                    Timber.e(e, "updateHistoryOrThrow() threw :O")
-                    errorEvents.postValue(e)
-                }
-            }
+        return tekHistoryUpdater.handleActivityResult(requestCode, resultCode, data).also {
+            Timber.d("tekHistoryUpdater.handleActivityResult(): %s", it)
         }
-
-        return result != UpdateResult.UNKNOWN_RESULT
     }
 
     @AssistedInject.Factory
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/TracingPermissionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/TracingPermissionHelper.kt
index 96d331d90f6714cc1b25dd58aa45e785b352a9e0..a7c968e5cdb089a8d133e814c3f146bcf986db76 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/TracingPermissionHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/TracingPermissionHelper.kt
@@ -2,91 +2,96 @@ package de.rki.coronawarnapp.nearby
 
 import android.app.Activity
 import android.content.Intent
+import androidx.annotation.VisibleForTesting
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.util.coroutine.AppScope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import timber.log.Timber
-import javax.inject.Inject
 
-class TracingPermissionHelper @Inject constructor(
+class TracingPermissionHelper @AssistedInject constructor(
+    @Assisted private val callback: Callback,
     private val enfClient: ENFClient,
     @AppScope private val scope: CoroutineScope
 ) {
 
-    var callback: Callback? = null
-
-    fun startTracing(
-        onUserPermissionRequired: (permissionRequest: (Activity) -> Unit) -> Unit
-    ) {
+    fun startTracing() {
         scope.launch {
             if (enfClient.isTracingEnabled.first()) {
-                callback?.onUpdateTracingStatus(true)
+                callback.onUpdateTracingStatus(true)
             } else {
-                enableTracing(onUserPermissionRequired)
+                if (isConsentGiven()) {
+                    enableTracing()
+                } else {
+                    callback.onTracingConsentRequired { given: Boolean ->
+                        Timber.tag(TAG).d("Consent result: $given")
+                        if (given) enableTracing()
+                    }
+                }
             }
         }
     }
 
-    private fun enableTracing(
-        onUserPermissionRequired: ((permissionRequest: (Activity) -> Unit) -> Unit)?
-    ) {
+    private fun enableTracing() {
         enfClient.setTracing(
             true,
-            onSuccess = { callback?.onUpdateTracingStatus(true) },
-            onError = { callback?.onError(it) },
+            onSuccess = { callback.onUpdateTracingStatus(true) },
+            onError = { callback.onError(it) },
             onPermissionRequired = { status ->
-                if (onUserPermissionRequired != null) {
-                    val permissionRequestTrigger: (Activity) -> Unit = {
-                        status.startResolutionForResult(it, TRACING_PERMISSION_REQUESTCODE)
-                    }
-                    onUserPermissionRequired(permissionRequestTrigger)
-                } else {
-                    callback?.onError(
-                        IllegalStateException("Permission were granted but we are still not allowed to enable tracing.")
-                    )
+                Timber.tag(TAG).d("Permission is required, starting user resolution.")
+                val permissionRequestTrigger: (Activity) -> Unit = {
+                    status.startResolutionForResult(it, TRACING_PERMISSION_REQUESTCODE)
                 }
+                callback.onPermissionRequired(permissionRequestTrigger)
             }
         )
     }
 
-    fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?): UpdateResult {
+    fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
         Timber.v(
             "handleActivityResult(requesutCode=%d, resultCode=%d, data=%s)",
             requestCode, resultCode, data
         )
         if (requestCode != TRACING_PERMISSION_REQUESTCODE) {
             Timber.tag(TAG).w("Not our request code ($requestCode): %s", data)
-            return UpdateResult.UNKNOWN_RESULT
+            return false
         }
 
-        return if (resultCode == Activity.RESULT_OK) {
+        if (resultCode == Activity.RESULT_OK) {
             Timber.tag(TAG).w("User granted permission (== RESULT_OK): %s", data)
-
-            enableTracing(null)
-            UpdateResult.PERMISSION_AVAILABLE
+            enableTracing()
         } else {
             Timber.tag(TAG).w("User declined permission (!= RESULT_OK): %s", data)
-
-            callback?.onUpdateTracingStatus(false)
-            UpdateResult.PERMISSION_DECLINED
+            callback.onUpdateTracingStatus(false)
         }
+        return true
     }
 
-    enum class UpdateResult {
-        PERMISSION_AVAILABLE,
-        PERMISSION_DECLINED,
-        UNKNOWN_RESULT
+    private fun isConsentGiven(): Boolean {
+        val firstTracingActivationAt = LocalData.initialTracingActivationTimestamp()
+        Timber.tag(TAG).v("isConsentGiven(): First tracing activationat: %d", firstTracingActivationAt)
+        return firstTracingActivationAt != null
     }
 
     interface Callback {
         fun onUpdateTracingStatus(isTracingEnabled: Boolean)
-
+        fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit)
+        fun onPermissionRequired(permissionRequest: (Activity) -> Unit)
         fun onError(error: Throwable)
     }
 
     companion object {
         private const val TAG = "TracingPermissionHelper"
-        const val TRACING_PERMISSION_REQUESTCODE = 3010
+
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        internal const val TRACING_PERMISSION_REQUESTCODE = 3010
+    }
+
+    @AssistedInject.Factory
+    interface Factory {
+        fun create(callback: Callback): TracingPermissionHelper
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/data/tekhistory/TEKHistoryUpdater.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/data/tekhistory/TEKHistoryUpdater.kt
index e8391c10cb11dc3c0b5ad3c12e762c6c3699c06a..0ffae418729480ee2df0354f881aa4701d993985 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/data/tekhistory/TEKHistoryUpdater.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/data/tekhistory/TEKHistoryUpdater.kt
@@ -2,73 +2,86 @@ package de.rki.coronawarnapp.submission.data.tekhistory
 
 import android.app.Activity
 import android.content.Intent
+import androidx.annotation.VisibleForTesting
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
 import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.nearby.ENFClient
+import de.rki.coronawarnapp.nearby.TracingPermissionHelper
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.coroutine.AppScope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import timber.log.Timber
 import java.util.UUID
-import javax.inject.Inject
 
-class TEKHistoryUpdater @Inject constructor(
+class TEKHistoryUpdater @AssistedInject constructor(
+    @Assisted val callback: Callback,
     private val tekHistoryStorage: TEKHistoryStorage,
     private val timeStamper: TimeStamper,
     private val enfClient: ENFClient,
+    private val tracingPermissionHelperFactory: TracingPermissionHelper.Factory,
     @AppScope private val scope: CoroutineScope
 ) {
 
-    var callback: Callback? = null
-
-    fun updateTEKHistoryOrRequestPermission(
-        onUserPermissionRequired: (permissionRequest: (Activity) -> Unit) -> Unit
-    ) {
-        scope.launch {
-            enfClient.getTEKHistoryOrRequestPermission(
-                onTEKHistoryAvailable = {
-                    updateHistoryAndTriggerCallback(it)
-                },
-                onPermissionRequired = { status ->
-                    val permissionRequestTrigger: (Activity) -> Unit = {
-                        status.startResolutionForResult(it, TEK_PERMISSION_REQUESTCODE)
-                    }
-                    onUserPermissionRequired(permissionRequestTrigger)
+    private val tracingPermissionHelper by lazy {
+        tracingPermissionHelperFactory.create(object : TracingPermissionHelper.Callback {
+            override fun onUpdateTracingStatus(isTracingEnabled: Boolean) {
+                if (isTracingEnabled) {
+                    updateTEKHistoryOrRequestPermission()
+                } else {
+                    Timber.tag(TAG).w("Can't start TEK update, tracing permission was declined.")
                 }
-            )
-        }
-    }
+            }
 
-    suspend fun updateHistoryOrThrow(): List<TemporaryExposureKey> {
-        return updateTEKHistory()
+            override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) =
+                callback.onTracingConsentRequired(onConsentResult)
+
+            override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) =
+                callback.onPermissionRequired(permissionRequest)
+
+            override fun onError(error: Throwable) = callback.onError(error)
+        })
     }
 
-    fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?): UpdateResult {
-        if (requestCode != TEK_PERMISSION_REQUESTCODE) {
-            Timber.tag(TAG).w("Not our request code ($requestCode): %s", data)
-            return UpdateResult.UNKNOWN_RESULT
-        }
-        return if (resultCode == Activity.RESULT_OK) {
-            Timber.tag(TAG).d("Permission granted (== RESULT_OK): %s", data)
-            updateHistoryAndTriggerCallback()
-            UpdateResult.PERMISSION_AVAILABLE
-        } else {
-            Timber.tag(TAG).i("Permission declined (!= RESULT_OK): %s", data)
-            callback?.onPermissionDeclined()
-            UpdateResult.PERMISSION_UNAVAILABLE
+    fun updateTEKHistoryOrRequestPermission() {
+        scope.launch {
+            if (!enfClient.isTracingEnabled.first()) {
+                Timber.tag(TAG).w("Tracing is disabled, enabling...")
+                tracingPermissionHelper.startTracing()
+            } else {
+                updateTEKHistoryInternal()
+            }
         }
     }
 
+    private suspend fun updateTEKHistoryInternal() {
+        enfClient.getTEKHistoryOrRequestPermission(
+            onTEKHistoryAvailable = {
+                Timber.tag(TAG).d("TEKS were directly available.")
+                updateHistoryAndTriggerCallback(it)
+            },
+            onPermissionRequired = { status ->
+                Timber.tag(TAG).d("TEK request requires user resolution.")
+                val permissionRequestTrigger: (Activity) -> Unit = {
+                    status.startResolutionForResult(it, TEK_PERMISSION_REQUEST)
+                }
+                callback.onPermissionRequired(permissionRequestTrigger)
+            }
+        )
+    }
+
     private fun updateHistoryAndTriggerCallback(availableTEKs: List<TemporaryExposureKey>? = null) {
         scope.launch {
             try {
                 val result = updateTEKHistory(availableTEKs)
-                callback?.onTEKAvailable(result)
+                callback.onTEKAvailable(result)
             } catch (e: Exception) {
-                callback?.onError(e)
+                callback.onError(e)
             }
         }
     }
@@ -80,15 +93,15 @@ class TEKHistoryUpdater @Inject constructor(
             val teks = availableTEKs ?: enfClient.getTEKHistory()
             Timber.i("Permission are available, storing TEK history.")
 
-            tekHistoryStorage.storeTEKData(
-                TEKHistoryStorage.TEKBatch(
-                    batchId = UUID.randomUUID().toString(),
-                    obtainedAt = timeStamper.nowUTC,
-                    keys = teks
+            teks.also {
+                tekHistoryStorage.storeTEKData(
+                    TEKHistoryStorage.TEKBatch(
+                        batchId = UUID.randomUUID().toString(),
+                        obtainedAt = timeStamper.nowUTC,
+                        keys = teks
+                    )
                 )
-            )
-
-            teks
+            }
         }
         return try {
             deferred.await()
@@ -99,20 +112,45 @@ class TEKHistoryUpdater @Inject constructor(
         }
     }
 
-    enum class UpdateResult {
-        PERMISSION_AVAILABLE,
-        PERMISSION_UNAVAILABLE,
-        UNKNOWN_RESULT
-    }
+    fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
+        val isTracingPermissionRequest = tracingPermissionHelper.handleActivityResult(requestCode, resultCode, data)
+        if (isTracingPermissionRequest) {
+            Timber.tag(TAG).d("Was tracing permission request, will try TEK update if tracing is now enabled.")
+            return true
+        }
 
-    companion object {
-        private const val TAG = "TEKHistoryUpdater"
-        const val TEK_PERMISSION_REQUESTCODE = 3011
+        if (requestCode != TEK_PERMISSION_REQUEST) {
+            Timber.tag(TAG).w("Not our request code ($requestCode): %s", data)
+            return false
+        }
+
+        if (resultCode == Activity.RESULT_OK) {
+            Timber.tag(TAG).d("We got TEK permission, now updating history.")
+            updateHistoryAndTriggerCallback()
+        } else {
+            Timber.tag(TAG).i("Permission declined (!= RESULT_OK): %s", data)
+            callback.onTEKPermissionDeclined()
+        }
+        return true
     }
 
     interface Callback {
         fun onTEKAvailable(teks: List<TemporaryExposureKey>)
-        fun onPermissionDeclined()
+        fun onTEKPermissionDeclined()
+        fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit)
+        fun onPermissionRequired(permissionRequest: (Activity) -> Unit)
         fun onError(error: Throwable)
     }
+
+    @AssistedInject.Factory
+    interface Factory {
+        fun create(callback: Callback): TEKHistoryUpdater
+    }
+
+    companion object {
+        private const val TAG = "TEKHistoryUpdater"
+
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        internal const val TEK_PERMISSION_REQUEST = 3011
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingConsentDialog.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingConsentDialog.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3c3fff6526dc61a8ec8490a29f44332878e9f765
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingConsentDialog.kt
@@ -0,0 +1,25 @@
+package de.rki.coronawarnapp.tracing.ui
+
+import android.content.Context
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.util.DialogHelper
+
+class TracingConsentDialog(private val context: Context) {
+
+    fun show(
+        onConsentGiven: () -> Unit,
+        onConsentDeclined: () -> Unit
+    ) {
+        val dialog = DialogHelper.DialogInstance(
+            context = context,
+            title = R.string.onboarding_tracing_headline_consent,
+            message = R.string.onboarding_tracing_body_consent,
+            positiveButton = R.string.onboarding_button_enable,
+            negativeButton = R.string.onboarding_button_cancel,
+            cancelable = true,
+            positiveButtonFunction = { onConsentGiven() },
+            negativeButtonFunction = { onConsentDeclined() }
+        )
+        DialogHelper.showDialog(dialog)
+    }
+}
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 078e462f9def169a578006b396056627fe9be5a0..1185467f27319dbbc79b55b2091021a7908b8c60 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
@@ -18,7 +18,7 @@ import timber.log.Timber
 
 class OnboardingTracingFragmentViewModel @AssistedInject constructor(
     private val interoperabilityRepository: InteroperabilityRepository,
-    private val tracingPermissionHelper: TracingPermissionHelper,
+    tracingPermissionHelperFactory: TracingPermissionHelper.Factory,
     dispatcherProvider: DispatcherProvider
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
@@ -27,19 +27,27 @@ class OnboardingTracingFragmentViewModel @AssistedInject constructor(
     val routeToScreen: SingleLiveEvent<OnboardingNavigationEvents> = SingleLiveEvent()
     val permissionRequestEvent = SingleLiveEvent<(Activity) -> Unit>()
 
-    init {
-        tracingPermissionHelper.callback = object : TracingPermissionHelper.Callback {
+    private val tracingPermissionHelper =
+        tracingPermissionHelperFactory.create(object : TracingPermissionHelper.Callback {
             override fun onUpdateTracingStatus(isTracingEnabled: Boolean) {
                 if (isTracingEnabled) {
                     routeToScreen.postValue(OnboardingNavigationEvents.NavigateToOnboardingTest)
                 }
             }
 
+            override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) {
+                // Tracing consent is given implicitly on this screen.
+                onConsentResult(true)
+            }
+
+            override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) {
+                permissionRequestEvent.postValue(permissionRequest)
+            }
+
             override fun onError(error: Throwable) {
                 Timber.e(error, "Failed to activate tracing during onboarding.")
             }
-        }
-    }
+        })
 
     fun saveInteroperabilityUsed() {
         interoperabilityRepository.saveInteroperabilityUsed()
@@ -65,9 +73,7 @@ class OnboardingTracingFragmentViewModel @AssistedInject constructor(
     }
 
     fun onActivateTracingClicked() {
-        tracingPermissionHelper.startTracing { permissionRequest ->
-            permissionRequestEvent.postValue(permissionRequest)
-        }
+        tracingPermissionHelper.startTracing()
     }
 
     fun showCancelDialog() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt
index cee30acba244ae26f0e6d7e6e3be05a084d2220c..3beb73ddb59ac30b9da5871b47514c2d8479f1bf 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt
@@ -8,6 +8,7 @@ import androidx.activity.OnBackPressedCallback
 import androidx.fragment.app.Fragment
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultAvailableBinding
+import de.rki.coronawarnapp.tracing.ui.TracingConsentDialog
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.doNavigate
@@ -65,6 +66,12 @@ class SubmissionTestResultAvailableFragment : Fragment(R.layout.fragment_submiss
         vm.showPermissionRequest.observe2(this) { permissionRequest ->
             permissionRequest.invoke(requireActivity())
         }
+        vm.showTracingConsentDialog.observe2(this) { onConsentResult ->
+            TracingConsentDialog(requireContext()).show(
+                onConsentGiven = { onConsentResult(true) },
+                onConsentDeclined = { onConsentResult(false) }
+            )
+        }
     }
 
     override fun onResume() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt
index a1100166ae014435d7ad929c5379055d3b334028..880901adc0a0915347802d800dfd2d368d2ad113 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableViewModel.kt
@@ -19,8 +19,8 @@ import timber.log.Timber
 
 class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
-    private val tekHistoryUpdater: TEKHistoryUpdater,
-    private val submissionRepository: SubmissionRepository
+    tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory,
+    submissionRepository: SubmissionRepository
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
     val routeToScreen = SingleLiveEvent<NavDirections>()
@@ -29,33 +29,42 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
     val consent = consentFlow.asLiveData(dispatcherProvider.Default)
     val showPermissionRequest = SingleLiveEvent<(Activity) -> Unit>()
     val showCloseDialog = SingleLiveEvent<Unit>()
+    val showTracingConsentDialog = SingleLiveEvent<(Boolean) -> Unit>()
 
-    init {
-        submissionRepository.refreshDeviceUIState(refreshTestResult = false)
+    private val tekHistoryUpdater = tekHistoryUpdaterFactory.create(object : TEKHistoryUpdater.Callback {
+        override fun onTEKAvailable(teks: List<TemporaryExposureKey>) {
+            routeToScreen.postValue(
+                SubmissionTestResultAvailableFragmentDirections
+                    .actionSubmissionTestResultAvailableFragmentToSubmissionTestResultConsentGivenFragment()
+            )
+        }
 
-        tekHistoryUpdater.callback = object : TEKHistoryUpdater.Callback {
-            override fun onTEKAvailable(teks: List<TemporaryExposureKey>) {
-                routeToScreen.postValue(
-                    SubmissionTestResultAvailableFragmentDirections
-                        .actionSubmissionTestResultAvailableFragmentToSubmissionTestResultConsentGivenFragment()
-                )
-            }
+        override fun onTEKPermissionDeclined() {
+            routeToScreen.postValue(
+                SubmissionTestResultAvailableFragmentDirections
+                    .actionSubmissionTestResultAvailableFragmentToSubmissionTestResultNoConsentFragment()
+            )
+        }
 
-            override fun onPermissionDeclined() {
-                routeToScreen.postValue(
-                    SubmissionTestResultAvailableFragmentDirections
-                        .actionSubmissionTestResultAvailableFragmentToSubmissionTestResultNoConsentFragment()
-                )
-            }
+        override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) {
+            showTracingConsentDialog.postValue(onConsentResult)
+        }
 
-            override fun onError(error: Throwable) {
-                Timber.e(error, "Failed to update TEKs.")
-                error.report(
-                    exceptionCategory = ExceptionCategory.EXPOSURENOTIFICATION,
-                    prefix = "SubmissionTestResultAvailableViewModel"
-                )
-            }
+        override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) {
+            showPermissionRequest.postValue(permissionRequest)
         }
+
+        override fun onError(error: Throwable) {
+            Timber.e(error, "Failed to update TEKs.")
+            error.report(
+                exceptionCategory = ExceptionCategory.EXPOSURENOTIFICATION,
+                prefix = "SubmissionTestResultAvailableViewModel"
+            )
+        }
+    })
+
+    init {
+        submissionRepository.refreshDeviceUIState(refreshTestResult = false)
     }
 
     fun goBack() {
@@ -81,9 +90,7 @@ class SubmissionTestResultAvailableViewModel @AssistedInject constructor(
     fun proceed() {
         launch {
             if (consentFlow.first()) {
-                tekHistoryUpdater.updateTEKHistoryOrRequestPermission { permissionRequest ->
-                    showPermissionRequest.postValue(permissionRequest)
-                }
+                tekHistoryUpdater.updateTEKHistoryOrRequestPermission()
             } else {
                 routeToScreen.postValue(
                     SubmissionTestResultAvailableFragmentDirections
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentFragment.kt
index 75d1643043d196c115303d45c39502163425b36e..eab307688d9b15cdaa88171bfa99134ef09bf80a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentFragment.kt
@@ -7,6 +7,7 @@ import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionNoConsentPositiveOtherWarningBinding
+import de.rki.coronawarnapp.tracing.ui.TracingConsentDialog
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.doNavigate
@@ -69,6 +70,13 @@ class SubmissionResultPositiveOtherWarningNoConsentFragment :
         viewModel.countryList.observe2(this) {
             binding.countryList.countries = it
         }
+
+        viewModel.showTracingConsentDialog.observe2(this) { onConsentResult ->
+            TracingConsentDialog(requireContext()).show(
+                onConsentGiven = { onConsentResult(true) },
+                onConsentDeclined = { onConsentResult(false) }
+            )
+        }
     }
 
     override fun onResume() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt
index dd6496f0bad62d2630378233f8601beeb4e48982..bd5ff6c8c211a58e00550e0c4788445fcec6e05f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningNoConsentViewModel.kt
@@ -21,8 +21,8 @@ import timber.log.Timber
 class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject constructor(
     dispatcherProvider: DispatcherProvider,
     private val enfClient: ENFClient,
-    private val tekHistoryUpdater: TEKHistoryUpdater,
-    private val interoperabilityRepository: InteroperabilityRepository
+    tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory,
+    interoperabilityRepository: InteroperabilityRepository
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
     val routeToScreen = SingleLiveEvent<NavDirections>()
@@ -34,25 +34,33 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con
     val countryList = interoperabilityRepository.countryList
         .asLiveData(context = dispatcherProvider.Default)
 
-    init {
-        tekHistoryUpdater.callback = object : TEKHistoryUpdater.Callback {
-            override fun onTEKAvailable(teks: List<TemporaryExposureKey>) {
-                routeToScreen.postValue(
-                    SubmissionResultPositiveOtherWarningNoConsentFragmentDirections
-                        .actionSubmissionResultPositiveOtherWarningNoConsentFragmentToSubmissionResultReadyFragment()
-                )
-            }
+    val showTracingConsentDialog = de.rki.coronawarnapp.ui.SingleLiveEvent<(Boolean) -> Unit>()
 
-            override fun onPermissionDeclined() {
-                // stay on screen
-            }
+    private val tekHistoryUpdater = tekHistoryUpdaterFactory.create(object : TEKHistoryUpdater.Callback {
+        override fun onTEKAvailable(teks: List<TemporaryExposureKey>) {
+            routeToScreen.postValue(
+                SubmissionResultPositiveOtherWarningNoConsentFragmentDirections
+                    .actionSubmissionResultPositiveOtherWarningNoConsentFragmentToSubmissionResultReadyFragment()
+            )
+        }
 
-            override fun onError(error: Throwable) {
-                Timber.e(error, "Couldn't access temporary exposure key history.")
-                error.report(ExceptionCategory.EXPOSURENOTIFICATION, "Failed to obtain TEKs.")
-            }
+        override fun onTEKPermissionDeclined() {
+            // stay on screen
         }
-    }
+
+        override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) {
+            showTracingConsentDialog.postValue(onConsentResult)
+        }
+
+        override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) {
+            showPermissionRequest.postValue(permissionRequest)
+        }
+
+        override fun onError(error: Throwable) {
+            Timber.e(error, "Couldn't access temporary exposure key history.")
+            error.report(ExceptionCategory.EXPOSURENOTIFICATION, "Failed to obtain TEKs.")
+        }
+    })
 
     fun onBackPressed() {
         routeToScreen.postValue(
@@ -64,9 +72,7 @@ class SubmissionResultPositiveOtherWarningNoConsentViewModel @AssistedInject con
     fun onConsentButtonClicked() {
         launch {
             if (enfClient.isTracingEnabled.first()) {
-                tekHistoryUpdater.updateTEKHistoryOrRequestPermission { permissionRequest ->
-                    showPermissionRequest.postValue(permissionRequest)
-                }
+                tekHistoryUpdater.updateTEKHistoryOrRequestPermission()
             } else {
                 showEnableTracingEvent.postValue(Unit)
             }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragment.kt
index 0e4c74efcd97a9c8c2d5f4c772a7589f15f01509..c64c9d4c58406c10c099243a3cfa0997b2a48ed7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragment.kt
@@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSettingsTracingBinding
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
+import de.rki.coronawarnapp.tracing.ui.TracingConsentDialog
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.tracing.settings.SettingsTracingFragmentViewModel.Event
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
@@ -26,7 +27,6 @@ import javax.inject.Inject
  *
  * @see SettingsViewModel
  * @see InternalExposureNotificationClient
- * @see InternalExposureNotificationPermissionHelper
  */
 class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), AutoInject {
 
@@ -62,8 +62,13 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), Au
         vm.events.observe2(this) {
             when (it) {
                 is Event.RequestPermissions -> it.permissionRequest.invoke(requireActivity())
-                Event.ShowConsentDialog -> showConsentDialog()
                 Event.ManualCheckingDialog -> showManualCheckingRequiredDialog()
+                is Event.TracingConsentDialog -> {
+                    TracingConsentDialog(requireContext()).show(
+                        onConsentGiven = { it.onConsentResult(true) },
+                        onConsentDeclined = { it.onConsentResult(false) }
+                    )
+                }
             }
         }
 
@@ -137,24 +142,6 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing), Au
         DialogHelper.showDialog(dialog)
     }
 
-    private fun showConsentDialog() {
-        val dialog = DialogHelper.DialogInstance(
-            context = requireActivity(),
-            title = R.string.onboarding_tracing_headline_consent,
-            message = R.string.onboarding_tracing_body_consent,
-            positiveButton = R.string.onboarding_button_enable,
-            negativeButton = R.string.onboarding_button_cancel,
-            cancelable = true,
-            positiveButtonFunction = {
-                vm.requestTracingTurnedOn()
-            },
-            negativeButtonFunction = {
-                vm.onTracingTurnedOff()
-            }
-        )
-        DialogHelper.showDialog(dialog)
-    }
-
     companion object {
         internal val TAG: String? = SettingsTracingFragment::class.simpleName
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragmentViewModel.kt
index c032b9e1de8409ac88d2c6dbef264a3efeaf070d..563d397714adf09a70656b3bef3cc6ff74f52a01 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragmentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/tracing/settings/SettingsTracingFragmentViewModel.kt
@@ -11,7 +11,6 @@ import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
 import de.rki.coronawarnapp.nearby.TracingPermissionHelper
-import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.tracing.GeneralTracingStatus
 import de.rki.coronawarnapp.ui.tracing.details.TracingDetailsState
 import de.rki.coronawarnapp.ui.tracing.details.TracingDetailsStateProvider
@@ -33,7 +32,7 @@ class SettingsTracingFragmentViewModel @AssistedInject constructor(
     tracingDetailsStateProvider: TracingDetailsStateProvider,
     tracingStatus: GeneralTracingStatus,
     private val backgroundPrioritization: BackgroundPrioritization,
-    private val tracingPermissionHelper: TracingPermissionHelper
+    tracingPermissionHelperFactory: TracingPermissionHelper.Factory
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
     val tracingDetailsState: LiveData<TracingDetailsState> = tracingDetailsStateProvider.state
@@ -57,8 +56,8 @@ class SettingsTracingFragmentViewModel @AssistedInject constructor(
         }
     }
 
-    init {
-        tracingPermissionHelper.callback = object : TracingPermissionHelper.Callback {
+    private val tracingPermissionHelper =
+        tracingPermissionHelperFactory.create(object : TracingPermissionHelper.Callback {
             override fun onUpdateTracingStatus(isTracingEnabled: Boolean) {
                 if (isTracingEnabled) {
                     // check if background processing is switched off,
@@ -71,29 +70,34 @@ class SettingsTracingFragmentViewModel @AssistedInject constructor(
                 isTracingSwitchChecked.postValue(isTracingEnabled)
             }
 
-            override fun onError(error: Throwable) {
-                Timber.w(error, "Failed to start tracing")
+            override fun onTracingConsentRequired(onConsentResult: (given: Boolean) -> Unit) {
+                events.postValue(Event.TracingConsentDialog { consentGiven ->
+                    if (!consentGiven) isTracingSwitchChecked.postValue(false)
+                    onConsentResult(consentGiven)
+                })
             }
-        }
-    }
 
-    private suspend fun turnTracingOff() {
-        InternalExposureNotificationClient.asyncStop()
-        BackgroundWorkScheduler.stopWorkScheduler()
-    }
+            override fun onPermissionRequired(permissionRequest: (Activity) -> Unit) {
+                events.postValue(Event.RequestPermissions(permissionRequest))
+            }
 
-    fun requestTracingTurnedOn() {
-        tracingPermissionHelper.startTracing { permissionRequest ->
-            events.postValue(Event.RequestPermissions(permissionRequest))
-        }
-    }
+            override fun onError(error: Throwable) {
+                Timber.w(error, "Failed to start tracing")
+            }
+        })
 
     fun onTracingToggled(isChecked: Boolean) {
         try {
             if (isChecked) {
-                onTracingTurnedOn()
+                tracingPermissionHelper.startTracing()
             } else {
-                onTracingTurnedOff()
+                isTracingSwitchChecked.postValue(false)
+                launch {
+                    if (InternalExposureNotificationClient.asyncIsEnabled()) {
+                        InternalExposureNotificationClient.asyncStop()
+                        BackgroundWorkScheduler.stopWorkScheduler()
+                    }
+                }
             }
         } catch (exception: Exception) {
             exception.report(
@@ -104,34 +108,13 @@ class SettingsTracingFragmentViewModel @AssistedInject constructor(
         }
     }
 
-    fun onTracingTurnedOff() {
-        isTracingSwitchChecked.postValue(false)
-        launch {
-            if (InternalExposureNotificationClient.asyncIsEnabled()) {
-                turnTracingOff()
-            }
-        }
-    }
-
-    private fun onTracingTurnedOn() {
-        // tracing was already activated
-        if (LocalData.initialTracingActivationTimestamp() != null) {
-            requestTracingTurnedOn()
-        } else {
-            // tracing was never activated
-            // ask for consent via dialog for initial tracing activation when tracing was not
-            // activated during onboarding
-            events.postValue(Event.ShowConsentDialog)
-        }
-    }
-
     fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
         tracingPermissionHelper.handleActivityResult(requestCode, resultCode, data)
     }
 
     sealed class Event {
         data class RequestPermissions(val permissionRequest: (Activity) -> Unit) : Event()
-        object ShowConsentDialog : Event()
+        data class TracingConsentDialog(val onConsentResult: (Boolean) -> Unit) : Event()
         object ManualCheckingDialog : Event()
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/TracingPermissionHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/TracingPermissionHelperTest.kt
index f40bde5af39ecfb9b9b4735e8d1328224fdac5d7..de30f6d1da854d5d9ae23031569e496be0f4f31f 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/TracingPermissionHelperTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/TracingPermissionHelperTest.kt
@@ -1,13 +1,20 @@
 package de.rki.coronawarnapp.nearby
 
 import android.app.Activity
+import com.google.android.gms.common.api.Status
+import de.rki.coronawarnapp.storage.LocalData
+import io.kotest.matchers.shouldBe
+import io.mockk.Called
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
 import io.mockk.coEvery
+import io.mockk.coVerifySequence
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.just
 import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.slot
 import io.mockk.verify
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.flowOf
@@ -24,34 +31,199 @@ class TracingPermissionHelperTest : BaseTest() {
         MockKAnnotations.init(this)
 
         coEvery { enfClient.isTracingEnabled } returns flowOf(false)
+        coEvery { enfClient.setTracing(any(), any(), any(), any()) } just Runs
+
+        mockkObject(LocalData)
+        every { LocalData.initialTracingActivationTimestamp() } returns 123L
     }
 
-    fun createInstance(scope: CoroutineScope) = TracingPermissionHelper(
+    fun createInstance(scope: CoroutineScope, callback: TracingPermissionHelper.Callback) = TracingPermissionHelper(
+        callback = callback,
         scope = scope,
         enfClient = enfClient
     )
 
     @Test
-    fun `request is forwarded if tracing is disabled`() = runBlockingTest {
-//        TODO()
+    fun `request is not forwarded if tracing is enabled`() = runBlockingTest {
+        coEvery { enfClient.isTracingEnabled } returns flowOf(true)
+
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.startTracing()
+
+        advanceUntilIdle()
+
+        coVerifySequence {
+            callback.onUpdateTracingStatus(true)
+        }
     }
 
     @Test
-    fun `request is not forwarded if tracing is enabled`() = runBlockingTest {
-        coEvery { enfClient.isTracingEnabled } returns flowOf(true)
+    fun `if consent is missing then we continue after it was given`() = runBlockingTest {
+        every { LocalData.initialTracingActivationTimestamp() } returns null
 
-        val callback = mockk<TracingPermissionHelper.Callback>()
-        every { callback.onUpdateTracingStatus(any()) } just Runs
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+        val consentCallbackSlot = slot<(Boolean) -> Unit>()
+        every { callback.onTracingConsentRequired(capture(consentCallbackSlot)) } just Runs
+        val instance = createInstance(scope = this, callback = callback)
 
-        val instance = createInstance(scope = this)
-        instance.callback = callback
+        instance.startTracing()
 
-        val permissionRequestListener = mockk<(permissionRequest: (Activity) -> Unit) -> Unit>()
+        consentCallbackSlot.captured(true)
 
-        instance.startTracing(permissionRequestListener)
+        coVerifySequence {
+            enfClient.isTracingEnabled
+            enfClient.setTracing(
+                enable = true,
+                onSuccess = any(),
+                onError = any(),
+                onPermissionRequired = any()
+            )
+        }
+    }
 
-        advanceUntilIdle()
+    @Test
+    fun `if consent was declined then we do nothing`() = runBlockingTest {
+        every { LocalData.initialTracingActivationTimestamp() } returns null
+
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+        val consentCallbackSlot = slot<(Boolean) -> Unit>()
+        every { callback.onTracingConsentRequired(capture(consentCallbackSlot)) } just Runs
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.startTracing()
+
+        consentCallbackSlot.captured(false)
+
+        coVerifySequence {
+            enfClient.isTracingEnabled
+        }
+    }
+
+    @Test
+    fun `if tracing is not yet enabled we forward to the enf client`() = runBlockingTest {
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.startTracing()
+
+        coVerifySequence {
+            enfClient.isTracingEnabled
+            enfClient.setTracing(
+                enable = true,
+                onSuccess = any(),
+                onError = any(),
+                onPermissionRequired = any()
+            )
+        }
+    }
+
+    @Test
+    fun `permission request is forwarded from enf client`() = runBlockingTest {
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+
+        val onPermissionRequiredCallback = slot<(Status) -> Unit>()
+        coEvery {
+            enfClient.setTracing(
+                enable = true,
+                onSuccess = any(),
+                onError = any(),
+                onPermissionRequired = capture(onPermissionRequiredCallback)
+            )
+        } just Runs
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.startTracing()
+        onPermissionRequiredCallback.captured.invoke(mockk())
+
+        coVerifySequence {
+            enfClient.isTracingEnabled
+            enfClient.setTracing(
+                enable = true,
+                onSuccess = any(),
+                onError = any(),
+                onPermissionRequired = onPermissionRequiredCallback.captured
+            )
+            callback.onPermissionRequired(any())
+        }
+    }
+
+    @Test
+    fun `errors from the enf client are forwarded`() = runBlockingTest {
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+        val onErrorCallback = slot<(Throwable) -> Unit>()
+        coEvery {
+            enfClient.setTracing(
+                enable = true,
+                onSuccess = any(),
+                onError = capture(onErrorCallback),
+                onPermissionRequired = any()
+            )
+        } just Runs
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.startTracing()
+        val error = IllegalStateException()
+        onErrorCallback.captured.invoke(error)
+
+        coVerifySequence {
+            enfClient.isTracingEnabled
+            enfClient.setTracing(
+                enable = true,
+                onSuccess = any(),
+                onError = onErrorCallback.captured,
+                onPermissionRequired = any()
+            )
+            callback.onError(error)
+        }
+    }
+
+    @Test
+    fun `unknown activity results are not consumed`() = runBlockingTest {
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.handleActivityResult(9999, Activity.RESULT_OK, mockk()) shouldBe false
+
+        verify { callback wasNot Called }
+    }
+
+    @Test
+    fun `positive activity results lead to new setTracing call`() = runBlockingTest {
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.handleActivityResult(
+            TracingPermissionHelper.TRACING_PERMISSION_REQUESTCODE,
+            Activity.RESULT_OK,
+            mockk()
+        ) shouldBe true
+
+        coVerifySequence {
+            enfClient.setTracing(
+                enable = true,
+                onSuccess = any(),
+                onError = any(),
+                onPermissionRequired = any()
+            )
+            callback wasNot Called
+        }
+    }
+
+    @Test
+    fun `negative activity results lead permission to direct callback`() = runBlockingTest {
+        val callback = mockk<TracingPermissionHelper.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.handleActivityResult(
+            TracingPermissionHelper.TRACING_PERMISSION_REQUESTCODE,
+            Activity.RESULT_CANCELED,
+            mockk()
+        ) shouldBe true
 
-        verify { callback.onUpdateTracingStatus(true) }
+        coVerifySequence {
+            callback.onUpdateTracingStatus(false)
+        }
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/data/tekhistory/TEKHistoryUpdaterTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/data/tekhistory/TEKHistoryUpdaterTest.kt
index e927516bd547f435720b7e8302e90bea6e9d1e8c..d195dd54a3ec3224b44e014534b86a2e8cb85da4 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/data/tekhistory/TEKHistoryUpdaterTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/data/tekhistory/TEKHistoryUpdaterTest.kt
@@ -1,33 +1,62 @@
 package de.rki.coronawarnapp.submission.data.tekhistory
 
+import android.app.Activity
+import android.content.Intent
+import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import de.rki.coronawarnapp.nearby.ENFClient
+import de.rki.coronawarnapp.nearby.TracingPermissionHelper
 import de.rki.coronawarnapp.util.TimeStamper
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.shouldNotBe
 import io.mockk.MockKAnnotations
 import io.mockk.Runs
 import io.mockk.coEvery
 import io.mockk.coVerify
+import io.mockk.coVerifySequence
+import io.mockk.every
 import io.mockk.impl.annotations.MockK
 import io.mockk.just
+import io.mockk.mockk
+import io.mockk.verify
+import io.mockk.verifySequence
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runBlockingTest
+import org.joda.time.Instant
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import testhelpers.BaseTest
 
 class TEKHistoryUpdaterTest : BaseTest() {
     @MockK lateinit var tekHistoryStorage: TEKHistoryStorage
+    @MockK lateinit var tracingPermissionHelper: TracingPermissionHelper
+    @MockK lateinit var tracingPermissionHelperFactory: TracingPermissionHelper.Factory
     @MockK lateinit var timeStamper: TimeStamper
     @MockK lateinit var enfClient: ENFClient
 
+    private val availableTEKs: List<TemporaryExposureKey> = listOf(mockk())
+
     @BeforeEach
     fun setup() {
         MockKAnnotations.init(this)
 
+        every { timeStamper.nowUTC } returns Instant.EPOCH
+
         coEvery { enfClient.getTEKHistoryOrRequestPermission(any(), any()) } just Runs
+        coEvery { enfClient.isTracingEnabled } returns flowOf(true)
+        coEvery { enfClient.getTEKHistory() } returns availableTEKs
+
+        coEvery { tekHistoryStorage.storeTEKData(any()) } just Runs
+
+        every { tracingPermissionHelperFactory.create(any()) } returns tracingPermissionHelper
+        coEvery { tracingPermissionHelper.startTracing() } just Runs
+        every { tracingPermissionHelper.handleActivityResult(any(), any(), any()) } returns false
     }
 
-    fun createInstance(scope: CoroutineScope) = TEKHistoryUpdater(
+    fun createInstance(scope: CoroutineScope, callback: TEKHistoryUpdater.Callback) = TEKHistoryUpdater(
+        callback = callback,
         scope = scope,
+        tracingPermissionHelperFactory = tracingPermissionHelperFactory,
         tekHistoryStorage = tekHistoryStorage,
         timeStamper = timeStamper,
         enfClient = enfClient
@@ -35,8 +64,10 @@ class TEKHistoryUpdaterTest : BaseTest() {
 
     @Test
     fun `request is forwaded to enf client`() = runBlockingTest {
-        val instance = createInstance(scope = this)
-        instance.updateTEKHistoryOrRequestPermission { }
+        val callback = mockk<TEKHistoryUpdater.Callback>()
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.updateTEKHistoryOrRequestPermission()
         coVerify {
             enfClient.getTEKHistoryOrRequestPermission(
                 any(),
@@ -44,4 +75,140 @@ class TEKHistoryUpdaterTest : BaseTest() {
             )
         }
     }
+
+    @Test
+    fun `if tracing is disabled then start tracing`() = runBlockingTest {
+        coEvery { enfClient.isTracingEnabled } returns flowOf(false)
+
+        every { tracingPermissionHelperFactory.create(any()) } returns tracingPermissionHelper
+
+        val callback = mockk<TEKHistoryUpdater.Callback>()
+        val instance = createInstance(scope = this, callback = callback)
+
+        instance.updateTEKHistoryOrRequestPermission()
+
+        verify {
+            tracingPermissionHelper.startTracing()
+        }
+    }
+
+    @Test
+    fun `tracing callbacks are forwarded via tek updater callbacks`() = runBlockingTest {
+        coEvery { enfClient.isTracingEnabled } returns flowOf(false)
+
+        var tracingCallback: TracingPermissionHelper.Callback? = null
+        every { tracingPermissionHelperFactory.create(any()) } answers {
+            tracingCallback = arg(0)
+            tracingPermissionHelper
+        }
+
+        val tekUpdaterCallback = mockk<TEKHistoryUpdater.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = tekUpdaterCallback)
+
+        instance.updateTEKHistoryOrRequestPermission()
+        tracingCallback shouldNotBe null
+
+        val consentRequest: (Boolean) -> Unit = { }
+        tracingCallback!!.onTracingConsentRequired(consentRequest)
+
+        val permissionRequest: (Activity) -> Unit = { }
+        tracingCallback!!.onPermissionRequired(permissionRequest)
+
+        verify {
+            tracingPermissionHelper.startTracing()
+            tekUpdaterCallback.onTracingConsentRequired(consentRequest)
+            tekUpdaterCallback.onPermissionRequired(permissionRequest)
+        }
+    }
+
+    @Test
+    fun `tracing permission results are forwarded to the tracing permissionhelper`() = runBlockingTest {
+        every { tracingPermissionHelper.handleActivityResult(any(), any(), any()) } returns true
+        val callback = mockk<TEKHistoryUpdater.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        val testIntent = mockk<Intent>()
+        instance.handleActivityResult(
+            requestCode = TracingPermissionHelper.TRACING_PERMISSION_REQUESTCODE,
+            resultCode = Activity.RESULT_OK,
+            data = testIntent
+        )
+
+        verify {
+            tracingPermissionHelper.handleActivityResult(
+                requestCode = TracingPermissionHelper.TRACING_PERMISSION_REQUESTCODE,
+                resultCode = Activity.RESULT_OK,
+                data = testIntent
+            )
+        }
+    }
+
+    @Test
+    fun `TEK activity results processed if not consumed by the tracing permissionhelper`() = runBlockingTest {
+        every { tracingPermissionHelper.handleActivityResult(any(), any(), any()) } returns false
+        val callback = mockk<TEKHistoryUpdater.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        val testIntent = mockk<Intent>()
+        instance.handleActivityResult(
+            requestCode = TEKHistoryUpdater.TEK_PERMISSION_REQUEST,
+            resultCode = Activity.RESULT_CANCELED,
+            data = testIntent
+        ) shouldBe true
+
+        verifySequence {
+            tracingPermissionHelper.handleActivityResult(
+                requestCode = TEKHistoryUpdater.TEK_PERMISSION_REQUEST,
+                resultCode = Activity.RESULT_CANCELED,
+                data = testIntent
+            )
+            callback.onTEKPermissionDeclined()
+        }
+    }
+
+    @Test
+    fun `unknown resultcodes are not consumed`() = runBlockingTest {
+        every { tracingPermissionHelper.handleActivityResult(any(), any(), any()) } returns false
+        val callback = mockk<TEKHistoryUpdater.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        val testIntent = mockk<Intent>()
+        instance.handleActivityResult(
+            requestCode = 123,
+            resultCode = Activity.RESULT_OK,
+            data = testIntent
+        ) shouldBe false
+
+        verify {
+            tracingPermissionHelper.handleActivityResult(
+                requestCode = 123,
+                resultCode = Activity.RESULT_OK,
+                data = testIntent
+            )
+        }
+    }
+
+    @Test
+    fun `positive TEK activity results trigger new update attempt`() = runBlockingTest {
+        every { tracingPermissionHelper.handleActivityResult(any(), any(), any()) } returns false
+        val callback = mockk<TEKHistoryUpdater.Callback>(relaxUnitFun = true)
+        val instance = createInstance(scope = this, callback = callback)
+
+        val testIntent = mockk<Intent>()
+        instance.handleActivityResult(
+            requestCode = TEKHistoryUpdater.TEK_PERMISSION_REQUEST,
+            resultCode = Activity.RESULT_OK,
+            data = testIntent
+        ) shouldBe true
+
+        coVerifySequence {
+            tracingPermissionHelper.handleActivityResult(
+                requestCode = TEKHistoryUpdater.TEK_PERMISSION_REQUEST,
+                resultCode = Activity.RESULT_OK,
+                data = testIntent
+            )
+            enfClient.getTEKHistory()
+            callback.onTEKAvailable(availableTEKs)
+        }
+    }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt
index d178119112b71b535a40cc8546a075225521bbcf..52eaba01d9e4e4cc591364bda8f01f505c8b6bb9 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testavailable/SubmissionTestResultAvailableViewModelTest.kt
@@ -28,13 +28,15 @@ class SubmissionTestResultAvailableViewModelTest : BaseTest() {
 
     @MockK lateinit var submissionRepository: SubmissionRepository
     @MockK lateinit var tekHistoryUpdater: TEKHistoryUpdater
+    @MockK lateinit var tekHistoryUpdaterFactory: TEKHistoryUpdater.Factory
 
     @BeforeEach
     fun setUp() {
         MockKAnnotations.init(this)
         every { submissionRepository.hasGivenConsentToSubmission } returns flowOf(true)
-        every { tekHistoryUpdater.callback = any() } just Runs
-        every { tekHistoryUpdater.updateTEKHistoryOrRequestPermission(any()) } just Runs
+
+        every { tekHistoryUpdaterFactory.create(any()) } returns tekHistoryUpdater
+        every { tekHistoryUpdater.updateTEKHistoryOrRequestPermission() } just Runs
 
         // TODO Check specific behavior
         every { submissionRepository.refreshDeviceUIState(any()) } just Runs
@@ -43,7 +45,7 @@ class SubmissionTestResultAvailableViewModelTest : BaseTest() {
     private fun createViewModel(): SubmissionTestResultAvailableViewModel = SubmissionTestResultAvailableViewModel(
         submissionRepository = submissionRepository,
         dispatcherProvider = TestDispatcherProvider,
-        tekHistoryUpdater = tekHistoryUpdater
+        tekHistoryUpdaterFactory = tekHistoryUpdaterFactory
     )
 
     @AfterEach
@@ -89,7 +91,7 @@ class SubmissionTestResultAvailableViewModelTest : BaseTest() {
 
         viewModel.proceed()
         verify {
-            tekHistoryUpdater.updateTEKHistoryOrRequestPermission(any())
+            tekHistoryUpdater.updateTEKHistoryOrRequestPermission()
         }
     }