From b4f32951f12c392c40ad72e3bf5cb05b24a13666 Mon Sep 17 00:00:00 2001
From: Matthias Urhahn <matthias.urhahn@sap.com>
Date: Wed, 7 Oct 2020 10:13:29 +0200
Subject: [PATCH] Introduce VM injection for home screen and enable screen
 testing (EXPOSUREAPP-2946, EXPOSUREAPP-2948) (#1320)

* Rename "MainFragment" to "HomeFragment"
Add VM injection.
Move initial logic into VM.

TODO: Move more code, write more tests.

* Additional refactoring, moving logic out of the Fragment.
Moving code into their own encapsulated routines/components.
Use composition to reuse TracingViewModel, SettingsViewModel and SubmissionViewModel

* Introduce @AppContext annotation to prevent anyone mistaking it for something else.

* Add VM to RiskDetailsFragment

* Simplify nested viewmodels, here: TracingViewModel.kt

* Add skeletons instrumentation test skeleton for single fragments with injection and mocking.

* Address PR comments.

* Introduce sealed events class, + some refactoring.

* Make the linter happy.
---
 Corona-Warn-App/build.gradle                  |  12 +-
 .../ui/main/home/HomeFragmentTest.kt          |  46 ++++
 .../java/testhelpers/BaseUITest.kt            |  16 ++
 .../FragmentTestModuleRegistrar.kt            |  11 +
 .../java/testhelpers/TestAppComponent.kt      |  24 ++
 .../java/testhelpers/TestApplication.kt       |  72 ++++++
 .../testhelpers/TestApplicationUIRunner.kt    |  12 +
 .../viewmodels/MockViewModelModule.kt         |  38 +++
 .../api/ui/TestForApiFragmentViewModel.kt     |   3 +-
 ...iskLevelCalculationFragmentCWAViewModel.kt |   3 +-
 .../appconfig/AppConfigModule.kt              |   3 +-
 .../appconfig/AppConfigStorage.kt             |   3 +-
 .../diagnosiskeys/DiagnosisKeysModule.kt      |   3 +-
 .../diagnosiskeys/storage/KeyCacheDatabase.kt |   3 +-
 .../storage/KeyCacheRepository.kt             |   3 +-
 .../storage/legacy/LegacyKeyCacheMigration.kt |   3 +-
 .../environment/EnvironmentSetup.kt           |   3 +-
 .../nearby/ENFClientLocalData.kt              |   3 +-
 .../de/rki/coronawarnapp/nearby/ENFModule.kt  |   3 +-
 .../coronawarnapp/storage/DeviceStorage.kt    |   3 +-
 .../storage/SettingsRepository.kt             |   3 +-
 .../submission/SubmissionModule.kt            |   3 +-
 .../ui/main/MainActivityModule.kt             |  10 +-
 .../rki/coronawarnapp/ui/main/MainFragment.kt | 234 ------------------
 .../ui/main/home/HomeFragment.kt              | 142 +++++++++++
 .../ui/main/home/HomeFragmentEvents.kt        |  11 +
 .../ui/main/home/HomeFragmentModule.kt        |  22 ++
 .../ui/main/home/HomeFragmentViewModel.kt     |  72 ++++++
 .../coronawarnapp/ui/main/home/HomeMenu.kt    |  46 ++++
 .../ui/main/home/TracingExplanationDialog.kt  |  37 +++
 .../{ => overview}/MainOverviewFragment.kt    |   3 +-
 .../ui/main/{ => share}/MainShareFragment.kt  |   3 +-
 .../ui/riskdetails/RiskDetailsFragment.kt     |  54 ++--
 .../riskdetails/RiskDetailsFragmentModule.kt  |  22 ++
 .../RiskDetailsFragmentViewModel.kt           |  35 +++
 .../ui/settings/SettingsTracingFragment.kt    |   9 +-
 .../ui/viewmodel/SettingsViewModel.kt         |   5 +-
 .../ui/viewmodel/SubmissionViewModel.kt       |   5 +-
 .../ui/viewmodel/TracingViewModel.kt          |  13 +-
 .../de/rki/coronawarnapp/util/DialogHelper.kt |  40 +--
 .../rki/coronawarnapp/util/WatchdogService.kt |   5 +-
 .../util/device/DefaultPowerManagement.kt     |   3 +-
 .../coronawarnapp/util/di/AndroidModule.kt    |   1 +
 .../rki/coronawarnapp/util/di/AppContext.kt   |   8 +
 .../security/EncryptedPreferencesFactory.kt   |   3 +-
 .../util/security/EncryptionErrorResetTool.kt |   3 +-
 .../util/ui/FragmentExtensions.kt             |   8 +
 .../coronawarnapp/util/ui/SmartLiveData.kt    |  14 +-
 .../util/viewmodel/CWAViewModel.kt            |  27 +-
 .../verification/VerificationModule.kt        |   3 +-
 .../{fragment_main.xml => fragment_home.xml}  |   2 +-
 .../src/main/res/navigation/nav_graph.xml     |   8 +-
 .../ui/main/home/MainFragmentViewModelTest.kt |   2 +
 .../java/testhelpers/BaseTest.kt              |   0
 .../java/testhelpers/logging/JUnitTree.kt     |   0
 55 files changed, 775 insertions(+), 348 deletions(-)
 create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
 create mode 100644 Corona-Warn-App/src/androidTest/java/testhelpers/BaseUITest.kt
 create mode 100644 Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt
 create mode 100644 Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt
 create mode 100644 Corona-Warn-App/src/androidTest/java/testhelpers/TestApplication.kt
 create mode 100644 Corona-Warn-App/src/androidTest/java/testhelpers/TestApplicationUIRunner.kt
 create mode 100644 Corona-Warn-App/src/androidTest/java/testhelpers/viewmodels/MockViewModelModule.kt
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentModule.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeMenu.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/TracingExplanationDialog.kt
 rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/{ => overview}/MainOverviewFragment.kt (92%)
 rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/{ => share}/MainShareFragment.kt (94%)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragmentModule.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragmentViewModel.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppContext.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt
 rename Corona-Warn-App/src/main/res/layout/{fragment_main.xml => fragment_home.xml} (99%)
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/main/home/MainFragmentViewModelTest.kt
 rename Corona-Warn-App/src/{test => testShared}/java/testhelpers/BaseTest.kt (100%)
 rename Corona-Warn-App/src/{test => testShared}/java/testhelpers/logging/JUnitTree.kt (100%)

diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle
index 78fd7e894..58504f84e 100644
--- a/Corona-Warn-App/build.gradle
+++ b/Corona-Warn-App/build.gradle
@@ -43,7 +43,7 @@ android {
         versionCode 43
         versionName "1.5.0"
 
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        testInstrumentationRunner "testhelpers.TestApplicationUIRunner"
 
         resConfigs "de", "en", "tr", "bg", "pl", "ro"
 
@@ -166,6 +166,12 @@ android {
                 srcDirs = ['src/device']
             }
         }
+        test {
+            java.srcDirs += "$projectDir/src/testShared/java"
+        }
+        androidTest {
+            java.srcDirs += "$projectDir/src/testShared/java"
+        }
     }
 
 }
@@ -237,6 +243,7 @@ dependencies {
     implementation 'com.google.dagger:dagger-android-support:2.28.1'
     kapt 'com.google.dagger:dagger-compiler:2.28.1'
     kapt 'com.google.dagger:dagger-android-processor:2.28.1'
+    kaptAndroidTest 'com.google.dagger:dagger-compiler:2.28.1'
 
     compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2'
     kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.2'
@@ -274,6 +281,7 @@ dependencies {
     androidTestImplementation "io.kotest:kotest-property-jvm:4.2.0"
 
     // Testing - Instrumentation
+    androidTestImplementation 'junit:junit:4.13'
     androidTestImplementation 'androidx.test:runner:1.2.0'
     androidTestImplementation 'androidx.test.ext:junit:1.1.1'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
@@ -282,7 +290,7 @@ dependencies {
     androidTestImplementation 'androidx.test.ext:junit:1.1.1'
     androidTestImplementation 'androidx.work:work-testing:2.3.4'
     androidTestImplementation "io.mockk:mockk-android:1.10.0"
-    debugImplementation 'androidx.fragment:fragment-testing:1.2.4'
+    debugImplementation 'androidx.fragment:fragment-testing:1.2.5'
 
     // Play Services
     implementation 'com.google.android.play:core:1.7.3'
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
new file mode 100644
index 000000000..baca08057
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
@@ -0,0 +1,46 @@
+package de.rki.coronawarnapp.ui.main.home
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class HomeFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: HomeFragmentViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+
+        setupMockViewModel(object : HomeFragmentViewModel.Factory {
+            override fun create(): HomeFragmentViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<HomeFragment>()
+
+        // ...
+    }
+}
+
+@Module
+abstract class HomeFragmentTestModule {
+    @ContributesAndroidInjector
+    abstract fun homeScreen(): HomeFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/BaseUITest.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/BaseUITest.kt
new file mode 100644
index 000000000..8f487ee1d
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/testhelpers/BaseUITest.kt
@@ -0,0 +1,16 @@
+package testhelpers
+
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import testhelpers.viewmodels.MockViewModelModule
+
+abstract class BaseUITest : BaseTest() {
+
+    inline fun <reified T : CWAViewModel> setupMockViewModel(factory: SimpleCWAViewModelFactory<T>) {
+        MockViewModelModule.CREATORS[T::class.java] = factory
+    }
+
+    fun clearAllViewModels() {
+        MockViewModelModule.CREATORS.clear()
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt
new file mode 100644
index 000000000..310356920
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt
@@ -0,0 +1,11 @@
+package testhelpers
+
+import dagger.Module
+import de.rki.coronawarnapp.ui.main.home.HomeFragmentTestModule
+
+@Module(
+    includes = [
+        HomeFragmentTestModule::class
+    ]
+)
+class FragmentTestModuleRegistrar
diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt
new file mode 100644
index 000000000..53c3c747c
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt
@@ -0,0 +1,24 @@
+package testhelpers
+
+import dagger.BindsInstance
+import dagger.Component
+import dagger.android.AndroidInjector
+import dagger.android.support.AndroidSupportInjectionModule
+import testhelpers.viewmodels.MockViewModelModule
+import javax.inject.Singleton
+
+@Component(
+    modules = [
+        AndroidSupportInjectionModule::class,
+        MockViewModelModule::class,
+        FragmentTestModuleRegistrar::class
+    ]
+)
+@Singleton
+interface TestAppComponent : AndroidInjector<TestApplication> {
+
+    @Component.Factory
+    interface Factory {
+        fun create(@BindsInstance app: TestApplication): TestAppComponent
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/TestApplication.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/TestApplication.kt
new file mode 100644
index 000000000..dc215f014
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/testhelpers/TestApplication.kt
@@ -0,0 +1,72 @@
+package testhelpers
+
+import android.app.Activity
+import android.app.Application
+import android.content.Context
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import dagger.android.AndroidInjector
+import dagger.android.DispatchingAndroidInjector
+import dagger.android.HasAndroidInjector
+import dagger.android.support.AndroidSupportInjection
+import de.rki.coronawarnapp.util.di.AutoInject
+import timber.log.Timber
+import javax.inject.Inject
+
+class TestApplication : Application(), HasAndroidInjector {
+    @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any>
+    override fun androidInjector(): AndroidInjector<Any> = androidInjector
+
+    private lateinit var component: TestAppComponent
+
+    override fun onCreate() {
+        super.onCreate()
+        component = DaggerTestAppComponent.factory().create(this)
+        component.inject(this)
+        setupActivityHook()
+    }
+
+    private fun setupActivityHook() {
+        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
+            override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) {
+                setupFragmentHook(activity)
+            }
+
+            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
+
+            override fun onActivityStarted(activity: Activity) = Unit
+
+            override fun onActivityResumed(activity: Activity) = Unit
+
+            override fun onActivityPaused(activity: Activity) = Unit
+
+            override fun onActivityStopped(activity: Activity) = Unit
+
+            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) = Unit
+
+            override fun onActivityDestroyed(activity: Activity) = Unit
+        })
+    }
+
+    private fun setupFragmentHook(activity: Activity) {
+        if (activity is FragmentActivity) {
+            activity.supportFragmentManager
+                .registerFragmentLifecycleCallbacks(object :
+                    FragmentManager.FragmentLifecycleCallbacks() {
+                    override fun onFragmentPreAttached(
+                        fm: FragmentManager,
+                        f: Fragment,
+                        context: Context
+                    ) {
+                        if (f is AutoInject) {
+                            Timber.d("Injecting %s", f)
+                            AndroidSupportInjection.inject(f)
+                        }
+                        super.onFragmentPreAttached(fm, f, context)
+                    }
+                }, true)
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/TestApplicationUIRunner.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/TestApplicationUIRunner.kt
new file mode 100644
index 000000000..1576aab58
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/testhelpers/TestApplicationUIRunner.kt
@@ -0,0 +1,12 @@
+package testhelpers
+
+import android.app.Application
+import android.content.Context
+import androidx.test.runner.AndroidJUnitRunner
+
+class TestApplicationUIRunner : AndroidJUnitRunner() {
+
+    override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
+        return super.newApplication(cl, TestApplication::class.java.name, context)
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/viewmodels/MockViewModelModule.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/viewmodels/MockViewModelModule.kt
new file mode 100644
index 000000000..1a4527b79
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/testhelpers/viewmodels/MockViewModelModule.kt
@@ -0,0 +1,38 @@
+package testhelpers.viewmodels
+
+import android.os.Bundle
+import androidx.lifecycle.SavedStateHandle
+import androidx.savedstate.SavedStateRegistryOwner
+import dagger.Module
+import dagger.Provides
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+
+@Module
+class MockViewModelModule {
+
+    @Provides
+    fun viewmodelFactoryProvider(): CWAViewModelFactoryProvider.Factory {
+        val factory = object : CWAViewModelFactoryProvider.Factory {
+            override fun create(
+                savedStateOwner: SavedStateRegistryOwner,
+                defaultSavedState: Bundle?,
+                assistAction: ((CWAViewModelFactory<out CWAViewModel>, SavedStateHandle) -> CWAViewModel)?
+            ): CWAViewModelFactoryProvider {
+                return CWAViewModelFactoryProvider(
+                    CREATORS,
+                    savedStateOwner,
+                    defaultSavedState,
+                    assistAction
+                )
+            }
+        }
+        return factory
+    }
+
+    companion object {
+        val CREATORS: MutableMap<Class<out CWAViewModel>, CWAViewModelFactory<out CWAViewModel>> =
+            mutableMapOf()
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt
index 5593009bb..47f89e72e 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt
@@ -15,6 +15,7 @@ import de.rki.coronawarnapp.test.api.ui.EnvironmentState.Companion.toEnvironment
 import de.rki.coronawarnapp.test.api.ui.LoggerState.Companion.toLoggerState
 import de.rki.coronawarnapp.transaction.RiskLevelTransaction
 import de.rki.coronawarnapp.util.CWADebug
+import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.ui.smartLiveData
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
@@ -24,7 +25,7 @@ import kotlinx.coroutines.launch
 import java.io.File
 
 class TestForApiFragmentViewModel @AssistedInject constructor(
-    private val context: Context,
+    @AppContext private val context: Context,
     private val envSetup: EnvironmentSetup
 ) : CWAViewModel() {
 
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt
index 7ba08ef6d..cd0b93eeb 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt
@@ -23,6 +23,7 @@ import de.rki.coronawarnapp.storage.RiskLevelRepository
 import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction
 import de.rki.coronawarnapp.transaction.RiskLevelTransaction
 import de.rki.coronawarnapp.util.KeyFileHelper
+import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.util.security.SecurityHelper
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
@@ -41,7 +42,7 @@ import kotlin.coroutines.suspendCoroutine
 class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
     @Assisted private val handle: SavedStateHandle,
     @Assisted private val exampleArg: String?,
-    private val context: Context, // App context
+    @AppContext private val context: Context, // App context
     private val enfClient: ENFClient,
     private val keyCacheRepository: KeyCacheRepository
 ) : CWAViewModel() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt
index aa295a352..d742d7370 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt
@@ -5,6 +5,7 @@ import dagger.Module
 import dagger.Provides
 import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient
 import de.rki.coronawarnapp.environment.download.DownloadCDNServerUrl
+import de.rki.coronawarnapp.util.di.AppContext
 import okhttp3.Cache
 import okhttp3.OkHttpClient
 import org.joda.time.Duration
@@ -20,7 +21,7 @@ class AppConfigModule {
     @Singleton
     @Provides
     fun provideAppConfigApi(
-        context: Context,
+        @AppContext context: Context,
         @DownloadCDNHttpClient client: OkHttpClient,
         @DownloadCDNServerUrl url: String,
         gsonConverterFactory: GsonConverterFactory
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigStorage.kt
index 44edccd9c..f81d9bee0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigStorage.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigStorage.kt
@@ -1,6 +1,7 @@
 package de.rki.coronawarnapp.appconfig
 
 import android.content.Context
+import de.rki.coronawarnapp.util.di.AppContext
 import timber.log.Timber
 import java.io.File
 import javax.inject.Inject
@@ -8,7 +9,7 @@ import javax.inject.Singleton
 
 @Singleton
 class AppConfigStorage @Inject constructor(
-    context: Context
+    @AppContext context: Context
 ) {
     private val configDir = File(context.filesDir, "appconfig_storage")
     private val configFile = File(configDir, "appconfig")
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/DiagnosisKeysModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/DiagnosisKeysModule.kt
index 213c479c3..c106f0a7e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/DiagnosisKeysModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/DiagnosisKeysModule.kt
@@ -8,6 +8,7 @@ import de.rki.coronawarnapp.diagnosiskeys.storage.legacy.KeyCacheLegacyDao
 import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient
 import de.rki.coronawarnapp.environment.download.DownloadCDNServerUrl
 import de.rki.coronawarnapp.storage.AppDatabase
+import de.rki.coronawarnapp.util.di.AppContext
 import okhttp3.OkHttpClient
 import retrofit2.Retrofit
 import retrofit2.converter.gson.GsonConverterFactory
@@ -31,7 +32,7 @@ class DiagnosisKeysModule {
 
     @Singleton
     @Provides
-    fun legacyKeyCacheDao(context: Context): KeyCacheLegacyDao {
+    fun legacyKeyCacheDao(@AppContext context: Context): KeyCacheLegacyDao {
         return AppDatabase.getInstance(context).dateDao()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheDatabase.kt
index e4be6ba47..ec23391b1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheDatabase.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheDatabase.kt
@@ -12,6 +12,7 @@ import androidx.room.RoomDatabase
 import androidx.room.TypeConverters
 import androidx.room.Update
 import de.rki.coronawarnapp.util.database.CommonConverters
+import de.rki.coronawarnapp.util.di.AppContext
 import javax.inject.Inject
 
 @Database(
@@ -45,7 +46,7 @@ abstract class KeyCacheDatabase : RoomDatabase() {
         suspend fun updateDownloadState(update: CachedKeyInfo.DownloadUpdate)
     }
 
-    class Factory @Inject constructor(private val context: Context) {
+    class Factory @Inject constructor(@AppContext private val context: Context) {
         /**
          * The fallback behavior is to reset the app as we only store exposure summaries
          * and cached references that are non-critical to app operation.
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheRepository.kt
index 4336c7ac8..c6f42e84e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheRepository.kt
@@ -23,6 +23,7 @@ import android.content.Context
 import android.database.sqlite.SQLiteConstraintException
 import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode
 import de.rki.coronawarnapp.util.TimeStamper
+import de.rki.coronawarnapp.util.di.AppContext
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import org.joda.time.LocalDate
@@ -35,7 +36,7 @@ import javax.inject.Singleton
 
 @Singleton
 class KeyCacheRepository @Inject constructor(
-    private val context: Context,
+    @AppContext private val context: Context,
     private val databaseFactory: KeyCacheDatabase.Factory,
     private val timeStamper: TimeStamper
 ) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/legacy/LegacyKeyCacheMigration.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/legacy/LegacyKeyCacheMigration.kt
index ca2cf9586..b7b4113d7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/legacy/LegacyKeyCacheMigration.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/storage/legacy/LegacyKeyCacheMigration.kt
@@ -5,6 +5,7 @@ import dagger.Lazy
 import de.rki.coronawarnapp.risk.TimeVariables
 import de.rki.coronawarnapp.util.HashExtensions.hashToMD5
 import de.rki.coronawarnapp.util.TimeStamper
+import de.rki.coronawarnapp.util.di.AppContext
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import org.joda.time.Duration
@@ -14,7 +15,7 @@ import java.io.File
 import javax.inject.Inject
 
 class LegacyKeyCacheMigration @Inject constructor(
-    private val context: Context,
+    @AppContext private val context: Context,
     private val legacyDao: Lazy<KeyCacheLegacyDao>,
     private val timeStamper: TimeStamper
 ) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
index ea54dfb6d..f8937d9b9 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
@@ -12,13 +12,14 @@ import de.rki.coronawarnapp.environment.EnvironmentSetup.EnvKey.VERIFICATION
 import de.rki.coronawarnapp.environment.EnvironmentSetup.EnvKey.VERIFICATION_KEYS
 import de.rki.coronawarnapp.environment.EnvironmentSetup.Type.Companion.toEnvironmentType
 import de.rki.coronawarnapp.util.CWADebug
+import de.rki.coronawarnapp.util.di.AppContext
 import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
 class EnvironmentSetup @Inject constructor(
-    private val context: Context
+    @AppContext private val context: Context
 ) {
 
     enum class EnvKey(val rawKey: String) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClientLocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClientLocalData.kt
index 26564ab4a..91d977c96 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClientLocalData.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClientLocalData.kt
@@ -2,13 +2,14 @@ package de.rki.coronawarnapp.nearby
 
 import android.content.Context
 import androidx.core.content.edit
+import de.rki.coronawarnapp.util.di.AppContext
 import org.joda.time.Instant
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
 class ENFClientLocalData @Inject constructor(
-    private val context: Context
+    @AppContext private val context: Context
 ) {
 
     private val prefs by lazy {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt
index 4b7094c8f..af07ccda5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt
@@ -7,6 +7,7 @@ import dagger.Module
 import dagger.Provides
 import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DefaultDiagnosisKeyProvider
 import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider
+import de.rki.coronawarnapp.util.di.AppContext
 import javax.inject.Singleton
 
 @Module
@@ -14,7 +15,7 @@ class ENFModule {
 
     @Singleton
     @Provides
-    fun exposureNotificationClient(context: Context): ExposureNotificationClient =
+    fun exposureNotificationClient(@AppContext context: Context): ExposureNotificationClient =
         Nearby.getExposureNotificationClient(context)
 
     @Singleton
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/DeviceStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/DeviceStorage.kt
index 412d5900f..5fd8d4b51 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/DeviceStorage.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/DeviceStorage.kt
@@ -8,6 +8,7 @@ import android.os.storage.StorageManager
 import android.text.format.Formatter
 import dagger.Reusable
 import de.rki.coronawarnapp.util.ApiLevel
+import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.util.storage.StatsFsProvider
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
@@ -19,7 +20,7 @@ import javax.inject.Inject
 
 @Reusable
 class DeviceStorage @Inject constructor(
-    private val context: Context,
+    @AppContext private val context: Context,
     private val apiLevel: ApiLevel,
     private val statsFsProvider: StatsFsProvider
 ) {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt
index 54c2f4150..37b9e7c67 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt
@@ -5,6 +5,7 @@ import androidx.core.app.NotificationManagerCompat
 import androidx.lifecycle.MutableLiveData
 import de.rki.coronawarnapp.util.BackgroundPrioritization
 import de.rki.coronawarnapp.util.ConnectivityHelper
+import de.rki.coronawarnapp.util.di.AppContext
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -18,7 +19,7 @@ import javax.inject.Singleton
  */
 @Singleton
 class SettingsRepository @Inject constructor(
-    private val context: Context,
+    @AppContext private val context: Context,
     private val backgroundPrioritization: BackgroundPrioritization
 ) {
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionModule.kt
index 437d565d6..cb5fd7406 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionModule.kt
@@ -9,6 +9,7 @@ import de.rki.coronawarnapp.http.HttpClientDefault
 import de.rki.coronawarnapp.http.RestrictedConnectionSpecs
 import de.rki.coronawarnapp.submission.server.SubmissionApiV1
 import de.rki.coronawarnapp.submission.server.SubmissionHttpClient
+import de.rki.coronawarnapp.util.di.AppContext
 import okhttp3.Cache
 import okhttp3.ConnectionSpec
 import okhttp3.OkHttpClient
@@ -33,7 +34,7 @@ class SubmissionModule {
     @Singleton
     @Provides
     fun provideSubmissionApi(
-        context: Context,
+        @AppContext context: Context,
         @SubmissionHttpClient client: OkHttpClient,
         @SubmissionCDNServerUrl url: String,
         protoConverterFactory: ProtoConverterFactory,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt
index 689223bc1..4db9d98e1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt
@@ -4,9 +4,17 @@ import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragment
 import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragmentModule
+import de.rki.coronawarnapp.ui.main.home.HomeFragmentModule
 import de.rki.coronawarnapp.ui.onboarding.OnboardingDeltaInteroperabilityModule
+import de.rki.coronawarnapp.ui.riskdetails.RiskDetailsFragmentModule
 
-@Module(includes = [OnboardingDeltaInteroperabilityModule::class])
+@Module(
+    includes = [
+        OnboardingDeltaInteroperabilityModule::class,
+        HomeFragmentModule::class,
+        RiskDetailsFragmentModule::class
+    ]
+)
 abstract class MainActivityModule {
 
     // activity specific injection module for future dependencies
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
deleted file mode 100644
index db82cafc9..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt
+++ /dev/null
@@ -1,234 +0,0 @@
-package de.rki.coronawarnapp.ui.main
-
-import android.os.Bundle
-import android.view.View
-import android.view.accessibility.AccessibilityEvent
-import android.widget.PopupMenu
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.lifecycleScope
-import androidx.navigation.fragment.findNavController
-import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.FragmentMainBinding
-import de.rki.coronawarnapp.risk.TimeVariables
-import de.rki.coronawarnapp.storage.LocalData
-import de.rki.coronawarnapp.timer.TimerHelper
-import de.rki.coronawarnapp.ui.doNavigate
-import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
-import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
-import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
-import de.rki.coronawarnapp.util.CWADebug
-import de.rki.coronawarnapp.util.DialogHelper
-import de.rki.coronawarnapp.util.ExternalActionHelper
-import de.rki.coronawarnapp.util.di.AppInjector
-import de.rki.coronawarnapp.util.errors.RecoveryByResetDialogFactory
-import de.rki.coronawarnapp.util.ui.viewBindingLazy
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-/**
- * After the user has finished the onboarding this fragment will be the heart of the application.
- * Three ViewModels are needed that this fragment shows all relevant information to the user.
- * Also the Menu is set here.
- *
- * @see tracingViewModel
- * @see settingsViewModel
- * @see submissionViewModel
- * @see PopupMenu
- */
-class MainFragment : Fragment(R.layout.fragment_main) {
-
-    private val tracingViewModel: TracingViewModel by activityViewModels()
-    private val settingsViewModel: SettingsViewModel by activityViewModels()
-    private val submissionViewModel: SubmissionViewModel by activityViewModels()
-    private val binding: FragmentMainBinding by viewBindingLazy()
-
-    private val errorResetTool by lazy {
-        AppInjector.component.errorResetTool
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        binding.tracingViewModel = tracingViewModel
-        binding.settingsViewModel = settingsViewModel
-        binding.submissionViewModel = submissionViewModel
-
-        setButtonOnClickListener()
-        setContentDescription()
-        checkShouldInteroperabilityBeOpened()
-        showOneTimeTracingExplanationDialog()
-    }
-
-    override fun onActivityCreated(savedInstanceState: Bundle?) {
-        super.onActivityCreated(savedInstanceState)
-
-        if (errorResetTool.isResetNoticeToBeShown) {
-            RecoveryByResetDialogFactory(this).showDialog(
-                detailsLink = R.string.errors_generic_text_catastrophic_error_encryption_failure,
-                onDismiss = {
-                    errorResetTool.isResetNoticeToBeShown = false
-                }
-            )
-        }
-    }
-
-    override fun onResume() {
-        super.onResume()
-        // refresh required data
-        tracingViewModel.refreshRiskLevel()
-        tracingViewModel.refreshExposureSummary()
-        tracingViewModel.refreshLastTimeDiagnosisKeysFetchedDate()
-        tracingViewModel.refreshIsTracingEnabled()
-        tracingViewModel.refreshActiveTracingDaysInRetentionPeriod()
-        TimerHelper.checkManualKeyRetrievalTimer()
-        submissionViewModel.refreshDeviceUIState()
-        tracingViewModel.refreshLastSuccessfullyCalculatedScore()
-        binding.mainScrollview.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
-    }
-
-    private fun setContentDescription() {
-        binding.mainHeaderShare.buttonIcon.contentDescription = getString(R.string.button_share)
-        binding.mainHeaderOptionsMenu.buttonIcon.contentDescription =
-            getString(R.string.button_menu)
-        binding.mainAbout.mainCard.contentDescription = getString(R.string.hint_external_webpage)
-    }
-
-    private fun setButtonOnClickListener() {
-        binding.mainTestUnregistered.submissionStatusCardUnregistered.setOnClickListener {
-            toSubmissionIntro()
-        }
-        binding.mainTestUnregistered.submissionStatusCardUnregisteredButton.setOnClickListener {
-            toSubmissionIntro()
-        }
-        binding.mainTestDone.submissionStatusCardDone.setOnClickListener {
-            findNavController().doNavigate(
-                MainFragmentDirections.actionMainFragmentToSubmissionDoneFragment()
-            )
-        }
-        binding.mainTestResult.submissionStatusCardContent.setOnClickListener {
-            toSubmissionResult()
-        }
-        binding.mainTestResult.submissionStatusCardContentButton.setOnClickListener {
-            toSubmissionResult()
-        }
-        binding.mainTestPositive.submissionStatusCardPositive.setOnClickListener {
-            toSubmissionResult()
-        }
-        binding.mainTestPositive.submissionStatusCardPositiveButton.setOnClickListener {
-            toSubmissionResult()
-        }
-        binding.mainTracing.setOnClickListener {
-            findNavController().doNavigate(MainFragmentDirections.actionMainFragmentToSettingsTracingFragment())
-        }
-        binding.mainRisk.riskCard.setOnClickListener {
-            findNavController().doNavigate(MainFragmentDirections.actionMainFragmentToRiskDetailsFragment())
-        }
-        binding.mainRisk.riskCardButtonUpdate.setOnClickListener {
-            tracingViewModel.refreshDiagnosisKeys()
-            settingsViewModel.updateManualKeyRetrievalEnabled(false)
-        }
-        binding.mainRisk.riskCardButtonEnableTracing.setOnClickListener {
-            findNavController().doNavigate(MainFragmentDirections.actionMainFragmentToSettingsTracingFragment())
-        }
-        binding.mainAbout.mainCard.setOnClickListener {
-            ExternalActionHelper.openUrl(this, requireContext().getString(R.string.main_about_link))
-        }
-        binding.mainHeaderShare.buttonIcon.setOnClickListener {
-            findNavController().doNavigate(MainFragmentDirections.actionMainFragmentToMainSharingFragment())
-        }
-        binding.mainHeaderOptionsMenu.buttonIcon.setOnClickListener {
-            showPopup(it)
-        }
-    }
-
-    private fun toSubmissionResult() {
-        findNavController().doNavigate(
-            MainFragmentDirections.actionMainFragmentToSubmissionResultFragment()
-        )
-    }
-
-    private fun toSubmissionIntro() {
-        findNavController().doNavigate(
-            MainFragmentDirections.actionMainFragmentToSubmissionIntroFragment()
-        )
-    }
-
-    private fun showPopup(view: View) = PopupMenu(requireContext(), view).apply {
-        inflate(R.menu.menu_main)
-        menu.findItem(R.id.menu_test).isVisible = CWADebug.isDeviceForTestersBuild
-        setOnMenuItemClickListener {
-            return@setOnMenuItemClickListener when (it.itemId) {
-                R.id.menu_help -> {
-                    findNavController().doNavigate(MainFragmentDirections.actionMainFragmentToMainOverviewFragment())
-                    true
-                }
-                R.id.menu_information -> {
-                    findNavController().doNavigate(MainFragmentDirections.actionMainFragmentToInformationFragment())
-                    true
-                }
-                R.id.menu_settings -> {
-                    findNavController().doNavigate(MainFragmentDirections.actionMainFragmentToSettingsFragment())
-                    true
-                }
-                R.id.menu_test -> {
-                    findNavController().doNavigate(MainFragmentDirections.actionMainFragmentToTestNavGraph())
-                    true
-                }
-                else -> super.onOptionsItemSelected(it)
-            }
-        }
-    }.show()
-
-    private fun checkShouldInteroperabilityBeOpened() {
-        if (!LocalData.isInteroperabilityShownAtLeastOnce) {
-            navigateToInteroperabilityFeature()
-        }
-    }
-
-    private fun navigateToInteroperabilityFeature() {
-        findNavController().doNavigate(
-            MainFragmentDirections.actionMainFragmentToOnboardingDeltaInteroperabilityFragment()
-        )
-    }
-
-    private fun showOneTimeTracingExplanationDialog() {
-
-        // check if the dialog explaining the tracing time was already shown
-        if (!LocalData.tracingExplanationDialogWasShown()) {
-
-            val activity = this.requireActivity()
-
-            lifecycleScope.launch {
-
-                // get all text strings and the current active tracing time
-                val infoPeriodLogged =
-                    getString(R.string.risk_details_information_body_period_logged)
-                val infoPeriodLoggedAssessment =
-                    getString(
-                        R.string.risk_details_information_body_period_logged_assessment,
-                        (TimeVariables.getActiveTracingDaysInRetentionPeriod()).toString()
-                    )
-                val infoFAQ = getString(R.string.risk_details_explanation_dialog_faq_body)
-
-                withContext(Dispatchers.Main) {
-
-                    // display the dialog
-                    DialogHelper.showDialog(
-                        DialogHelper.DialogInstance(
-                            activity,
-                            getString(R.string.risk_details_explanation_dialog_title),
-                            "$infoPeriodLogged\n\n$infoPeriodLoggedAssessment\n\n$infoFAQ",
-                            getString(R.string.errors_generic_button_positive),
-                            null,
-                            null,
-                            {
-                                LocalData.tracingExplanationDialogWasShown(true)
-                            },
-                            {}
-                        ))
-                }
-            }
-        }
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt
new file mode 100644
index 000000000..da0071854
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt
@@ -0,0 +1,142 @@
+package de.rki.coronawarnapp.ui.main.home
+
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentHomeBinding
+import de.rki.coronawarnapp.util.ExternalActionHelper
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.errors.RecoveryByResetDialogFactory
+import de.rki.coronawarnapp.util.ui.doNavigate
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import javax.inject.Inject
+
+/**
+ * After the user has finished the onboarding this fragment will be the heart of the application.
+ * Three ViewModels are needed that this fragment shows all relevant information to the user.
+ * Also the Menu is set here.
+ */
+class HomeFragment : Fragment(R.layout.fragment_home), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: HomeFragmentViewModel by cwaViewModels { viewModelFactory }
+
+    private val binding: FragmentHomeBinding by viewBindingLazy()
+
+    @Inject lateinit var homeMenu: HomeMenu
+    @Inject lateinit var tracingExplanationDialog: TracingExplanationDialog
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        binding.tracingViewModel = vm.tracingViewModel
+        binding.settingsViewModel = vm.settingsViewModel
+        binding.submissionViewModel = vm.submissionViewModel
+
+        setupToolbar()
+
+        setupTestResultCard()
+
+        binding.mainTracing.setOnClickListener {
+            doNavigate(HomeFragmentDirections.actionMainFragmentToSettingsTracingFragment())
+        }
+
+        setupRiskCard()
+
+        binding.mainAbout.mainCard.apply {
+            setOnClickListener {
+                ExternalActionHelper.openUrl(this@HomeFragment, getString(R.string.main_about_link))
+            }
+            contentDescription = getString(R.string.hint_external_webpage)
+        }
+
+        vm.events.observe2(this) {
+            when (it) {
+                HomeFragmentEvents.ShowInteropDeltaOnboarding -> {
+                    doNavigate(
+                        HomeFragmentDirections.actionMainFragmentToOnboardingDeltaInteroperabilityFragment()
+                    )
+                }
+                is HomeFragmentEvents.ShowTracingExplanation -> {
+                    tracingExplanationDialog.show(it.activeTracingDaysInRetentionPeriod) {
+                        vm.tracingExplanationWasShown()
+                    }
+                }
+                HomeFragmentEvents.ShowErrorResetDialog -> {
+                    RecoveryByResetDialogFactory(this).showDialog(
+                        detailsLink = R.string.errors_generic_text_catastrophic_error_encryption_failure,
+                        onDismiss = { vm.errorResetDialogDismissed() }
+                    )
+                }
+            }
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        vm.refreshRequiredData()
+        binding.mainScrollview.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
+    }
+
+    private fun setupRiskCard() {
+        binding.mainRisk.apply {
+            riskCard.setOnClickListener {
+                doNavigate(HomeFragmentDirections.actionMainFragmentToRiskDetailsFragment())
+            }
+            riskCardButtonUpdate.setOnClickListener {
+                vm.tracingViewModel.refreshDiagnosisKeys()
+                vm.settingsViewModel.updateManualKeyRetrievalEnabled(false)
+            }
+            riskCardButtonEnableTracing.setOnClickListener {
+                doNavigate(HomeFragmentDirections.actionMainFragmentToSettingsTracingFragment())
+            }
+        }
+    }
+
+    private fun setupTestResultCard() {
+        binding.apply {
+            val toSubmissionResult = {
+                doNavigate(HomeFragmentDirections.actionMainFragmentToSubmissionResultFragment())
+            }
+            mainTestUnregistered.apply {
+                val toSubmissionIntro = {
+                    doNavigate(HomeFragmentDirections.actionMainFragmentToSubmissionIntroFragment())
+                }
+                submissionStatusCardUnregistered.setOnClickListener { toSubmissionIntro() }
+                submissionStatusCardUnregisteredButton.setOnClickListener { toSubmissionIntro() }
+            }
+
+            mainTestDone.submissionStatusCardDone.setOnClickListener {
+                doNavigate(HomeFragmentDirections.actionMainFragmentToSubmissionDoneFragment())
+            }
+            mainTestResult.apply {
+                submissionStatusCardContent.setOnClickListener { toSubmissionResult() }
+                submissionStatusCardContentButton.setOnClickListener { toSubmissionResult() }
+            }
+
+            mainTestPositive.apply {
+                submissionStatusCardPositive.setOnClickListener { toSubmissionResult() }
+                submissionStatusCardPositiveButton.setOnClickListener { toSubmissionResult() }
+            }
+        }
+    }
+
+    private fun setupToolbar() {
+        binding.mainHeaderShare.buttonIcon.apply {
+            contentDescription = getString(R.string.button_share)
+            setOnClickListener {
+                doNavigate(HomeFragmentDirections.actionMainFragmentToMainSharingFragment())
+            }
+        }
+
+        binding.mainHeaderOptionsMenu.buttonIcon.apply {
+            contentDescription = getString(R.string.button_menu)
+            setOnClickListener { homeMenu.showMenuFor(it) }
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt
new file mode 100644
index 000000000..cb0ea79e3
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt
@@ -0,0 +1,11 @@
+package de.rki.coronawarnapp.ui.main.home
+
+sealed class HomeFragmentEvents {
+    object ShowInteropDeltaOnboarding : HomeFragmentEvents()
+
+    data class ShowTracingExplanation(
+        val activeTracingDaysInRetentionPeriod: Long
+    ) : HomeFragmentEvents()
+
+    object ShowErrorResetDialog : HomeFragmentEvents()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentModule.kt
new file mode 100644
index 000000000..0032c0aa4
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentModule.kt
@@ -0,0 +1,22 @@
+package de.rki.coronawarnapp.ui.main.home
+
+import dagger.Binds
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class HomeFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(HomeFragmentViewModel::class)
+    abstract fun homeFragment(
+        factory: HomeFragmentViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+
+    @ContributesAndroidInjector
+    abstract fun homeScreen(): HomeFragment
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt
new file mode 100644
index 000000000..fb2bdc4d2
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt
@@ -0,0 +1,72 @@
+package de.rki.coronawarnapp.ui.main.home
+
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.risk.TimeVariables
+import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.timer.TimerHelper
+import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowErrorResetDialog
+import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowInteropDeltaOnboarding
+import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowTracingExplanation
+import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
+import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
+import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class HomeFragmentViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider,
+    private val errorResetTool: EncryptionErrorResetTool,
+    val tracingViewModel: TracingViewModel,
+    val settingsViewModel: SettingsViewModel,
+    val submissionViewModel: SubmissionViewModel
+) : CWAViewModel(
+    dispatcherProvider = dispatcherProvider,
+    childViewModels = listOf(tracingViewModel, settingsViewModel, submissionViewModel)
+) {
+
+    val events = SingleLiveEvent<HomeFragmentEvents>()
+
+    init {
+        if (!LocalData.isInteroperabilityShownAtLeastOnce) {
+            events.postValue(ShowInteropDeltaOnboarding)
+        } else {
+            launch {
+                if (!LocalData.tracingExplanationDialogWasShown()) {
+                    events.postValue(
+                        ShowTracingExplanation(TimeVariables.getActiveTracingDaysInRetentionPeriod())
+                    )
+                }
+            }
+            launch {
+                if (errorResetTool.isResetNoticeToBeShown) {
+                    events.postValue(ShowErrorResetDialog)
+                }
+            }
+        }
+    }
+
+    fun errorResetDialogDismissed() {
+        errorResetTool.isResetNoticeToBeShown = false
+    }
+
+    fun refreshRequiredData() {
+        tracingViewModel.refreshRiskLevel()
+        tracingViewModel.refreshExposureSummary()
+        tracingViewModel.refreshLastTimeDiagnosisKeysFetchedDate()
+        tracingViewModel.refreshIsTracingEnabled()
+        tracingViewModel.refreshActiveTracingDaysInRetentionPeriod()
+        TimerHelper.checkManualKeyRetrievalTimer()
+        submissionViewModel.refreshDeviceUIState()
+        tracingViewModel.refreshLastSuccessfullyCalculatedScore()
+    }
+
+    fun tracingExplanationWasShown() {
+        LocalData.tracingExplanationDialogWasShown(true)
+    }
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<HomeFragmentViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeMenu.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeMenu.kt
new file mode 100644
index 000000000..a20df5ef4
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeMenu.kt
@@ -0,0 +1,46 @@
+package de.rki.coronawarnapp.ui.main.home
+
+import android.content.Context
+import android.view.View
+import android.widget.PopupMenu
+import androidx.navigation.NavController
+import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.doNavigate
+import de.rki.coronawarnapp.util.CWADebug
+import javax.inject.Inject
+
+class HomeMenu @Inject constructor(
+    private val homeFragment: HomeFragment
+) {
+    private val context: Context = homeFragment.requireContext()
+
+    private val navController: NavController
+        get() = homeFragment.findNavController()
+
+    fun showMenuFor(view: View) = PopupMenu(context, view).apply {
+        inflate(R.menu.menu_main)
+        menu.findItem(R.id.menu_test).isVisible = CWADebug.isDeviceForTestersBuild
+        setOnMenuItemClickListener {
+            return@setOnMenuItemClickListener when (it.itemId) {
+                R.id.menu_help -> {
+                    navController.doNavigate(HomeFragmentDirections.actionMainFragmentToMainOverviewFragment())
+                    true
+                }
+                R.id.menu_information -> {
+                    navController.doNavigate(HomeFragmentDirections.actionMainFragmentToInformationFragment())
+                    true
+                }
+                R.id.menu_settings -> {
+                    navController.doNavigate(HomeFragmentDirections.actionMainFragmentToSettingsFragment())
+                    true
+                }
+                R.id.menu_test -> {
+                    navController.doNavigate(HomeFragmentDirections.actionMainFragmentToTestNavGraph())
+                    true
+                }
+                else -> homeFragment.onOptionsItemSelected(it)
+            }
+        }
+    }.show()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/TracingExplanationDialog.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/TracingExplanationDialog.kt
new file mode 100644
index 000000000..a9bdd4dac
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/TracingExplanationDialog.kt
@@ -0,0 +1,37 @@
+package de.rki.coronawarnapp.ui.main.home
+
+import android.content.Context
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.util.DialogHelper
+import javax.inject.Inject
+
+class TracingExplanationDialog @Inject constructor(
+    private val homeFragment: HomeFragment
+) {
+    private val context: Context
+        get() = homeFragment.requireContext()
+
+    fun show(activeTracingDaysInRetentionPeriod: Long, onPositive: () -> Unit) {
+        // get all text strings and the current active tracing time
+        val infoPeriodLogged =
+            context.getString(R.string.risk_details_information_body_period_logged)
+        val infoPeriodLoggedAssessment =
+            context.getString(
+                R.string.risk_details_information_body_period_logged_assessment,
+                activeTracingDaysInRetentionPeriod.toString()
+            )
+        val infoFAQ = context.getString(R.string.risk_details_explanation_dialog_faq_body)
+
+        val data = DialogHelper.DialogInstance(
+            context = context,
+            title = context.getString(R.string.risk_details_explanation_dialog_title),
+            message = "$infoPeriodLogged\n\n$infoPeriodLoggedAssessment\n\n$infoFAQ",
+            positiveButton = context.getString(R.string.errors_generic_button_positive),
+            negativeButton = null,
+            cancelable = null,
+            positiveButtonFunction = onPositive,
+            negativeButtonFunction = {}
+        )
+        DialogHelper.showDialog(data)
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/overview/MainOverviewFragment.kt
similarity index 92%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/overview/MainOverviewFragment.kt
index c99a5dba3..0a4dce381 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/overview/MainOverviewFragment.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.main
+package de.rki.coronawarnapp.ui.main.overview
 
 import android.os.Bundle
 import android.view.View
@@ -6,6 +6,7 @@ import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentMainOverviewBinding
+import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
 
 /**
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/share/MainShareFragment.kt
similarity index 94%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/share/MainShareFragment.kt
index a82e0c320..6616b55c8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/share/MainShareFragment.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.main
+package de.rki.coronawarnapp.ui.main.share
 
 import android.os.Bundle
 import android.view.View
@@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentMainShareBinding
+import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.ExternalActionHelper
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragment.kt
index b3846cbb9..0d53843a0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragment.kt
@@ -4,17 +4,19 @@ import android.os.Bundle
 import android.view.View
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentRiskDetailsBinding
-import de.rki.coronawarnapp.timer.TimerHelper
 import de.rki.coronawarnapp.ui.doNavigate
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
-import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.util.convertToHyperlink
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import javax.inject.Inject
 
 /**
  * This is the detail view of the risk card if additional information for the user.
@@ -22,51 +24,37 @@ import de.rki.coronawarnapp.util.convertToHyperlink
  * @see TracingViewModel
  * @see SettingsViewModel
  */
-class RiskDetailsFragment : Fragment(R.layout.fragment_risk_details) {
+class RiskDetailsFragment : Fragment(R.layout.fragment_risk_details), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: RiskDetailsFragmentViewModel by cwaViewModels { viewModelFactory }
 
-    private val tracingViewModel: TracingViewModel by activityViewModels()
-    private val settingsViewModel: SettingsViewModel by activityViewModels()
     private val binding: FragmentRiskDetailsBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        binding.tracingViewModel = tracingViewModel
-        binding.settingsViewModel = settingsViewModel
-        setButtonOnClickListeners()
-        setUpWebLinks()
-    }
+        binding.tracingViewModel = vm.tracingViewModel
+        binding.settingsViewModel = vm.settingsViewModel
 
-    override fun onResume() {
-        super.onResume()
-        // refresh required data
-        tracingViewModel.refreshRiskLevel()
-        tracingViewModel.refreshExposureSummary()
-        tracingViewModel.refreshLastTimeDiagnosisKeysFetchedDate()
-        TimerHelper.checkManualKeyRetrievalTimer()
-        tracingViewModel.refreshActiveTracingDaysInRetentionPeriod()
-        binding.riskDetailsContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
-    }
-
-    /**
-     * Make the links clickable and convert to hyperlink
-     */
-    private fun setUpWebLinks() {
-        binding.riskDetailsInformationLowriskBodyUrl
-            .convertToHyperlink(getString(R.string.risk_details_explanation_faq_body_with_link))
-    }
-
-    private fun setButtonOnClickListeners() {
         binding.riskDetailsHeaderButtonBack.setOnClickListener {
             (activity as MainActivity).goBack()
         }
         binding.riskDetailsButtonUpdate.setOnClickListener {
-            tracingViewModel.refreshDiagnosisKeys()
-            settingsViewModel.updateManualKeyRetrievalEnabled(false)
+            vm.updateRiskDetails()
         }
         binding.riskDetailsButtonEnableTracing.setOnClickListener {
             findNavController().doNavigate(
                 RiskDetailsFragmentDirections.actionRiskDetailsFragmentToSettingsTracingFragment()
             )
         }
+
+        binding.riskDetailsInformationLowriskBodyUrl
+            .convertToHyperlink(getString(R.string.risk_details_explanation_faq_body_with_link))
+    }
+
+    override fun onResume() {
+        super.onResume()
+        vm.refreshData()
+        binding.riskDetailsContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragmentModule.kt
new file mode 100644
index 000000000..1cf96b5c4
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragmentModule.kt
@@ -0,0 +1,22 @@
+package de.rki.coronawarnapp.ui.riskdetails
+
+import dagger.Binds
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class RiskDetailsFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(RiskDetailsFragmentViewModel::class)
+    abstract fun homeFragment(
+        factory: RiskDetailsFragmentViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+
+    @ContributesAndroidInjector
+    abstract fun riskDetails(): RiskDetailsFragment
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragmentViewModel.kt
new file mode 100644
index 000000000..cfe5ccd3f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/riskdetails/RiskDetailsFragmentViewModel.kt
@@ -0,0 +1,35 @@
+package de.rki.coronawarnapp.ui.riskdetails
+
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.timer.TimerHelper
+import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
+import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class RiskDetailsFragmentViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider,
+    val tracingViewModel: TracingViewModel,
+    val settingsViewModel: SettingsViewModel
+) : CWAViewModel(
+    dispatcherProvider = dispatcherProvider,
+    childViewModels = listOf(tracingViewModel, settingsViewModel)
+) {
+
+    fun refreshData() {
+        tracingViewModel.refreshRiskLevel()
+        tracingViewModel.refreshExposureSummary()
+        tracingViewModel.refreshLastTimeDiagnosisKeysFetchedDate()
+        TimerHelper.checkManualKeyRetrievalTimer()
+        tracingViewModel.refreshActiveTracingDaysInRetentionPeriod()
+    }
+
+    fun updateRiskDetails() {
+        tracingViewModel.refreshDiagnosisKeys()
+        settingsViewModel.updateManualKeyRetrievalEnabled(false)
+    }
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<RiskDetailsFragmentViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
index 7f496e7d2..e25321632 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
@@ -22,7 +22,6 @@ import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.ExternalActionHelper
 import de.rki.coronawarnapp.util.formatter.formatTracingSwitchEnabled
-import de.rki.coronawarnapp.util.ui.observe2
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import kotlinx.coroutines.launch
@@ -53,12 +52,6 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing),
         binding.tracingViewModel = tracingViewModel
         binding.settingsViewModel = settingsViewModel
 
-        tracingViewModel.navigateToInteroperability.observe2(this) {
-            if (it) {
-                navigateToInteroperability()
-            }
-        }
-
         setButtonOnClickListener()
     }
 
@@ -130,7 +123,7 @@ class SettingsTracingFragment : Fragment(R.layout.fragment_settings_tracing),
             ExternalActionHelper.toMainSettings(requireContext())
         }
         interoperability.setOnClickListener {
-            tracingViewModel.onInteroperabilitySettingPressed()
+            navigateToInteroperability()
         }
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt
index fa6bde7ff..2f9880069 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt
@@ -1,16 +1,17 @@
 package de.rki.coronawarnapp.ui.viewmodel
 
 import androidx.lifecycle.LiveData
-import androidx.lifecycle.ViewModel
 import de.rki.coronawarnapp.storage.SettingsRepository
 import de.rki.coronawarnapp.util.di.AppInjector
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import javax.inject.Inject
 
 /**
  * ViewModel for everything settings related.
  *
  * @see SettingsRepository
  */
-class SettingsViewModel : ViewModel() {
+class SettingsViewModel @Inject constructor() : CWAViewModel() {
 
     private val settingsRepository by lazy {
         AppInjector.component.settingsRepository
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
index 5f2c6cbda..0d985fe35 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
@@ -2,7 +2,6 @@ package de.rki.coronawarnapp.ui.viewmodel
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import de.rki.coronawarnapp.exception.ExceptionCategory
@@ -23,12 +22,14 @@ import de.rki.coronawarnapp.ui.submission.SymptomIntroductionEvent
 import de.rki.coronawarnapp.util.DeviceUIState
 import de.rki.coronawarnapp.util.Event
 import de.rki.coronawarnapp.util.di.AppInjector
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import kotlinx.coroutines.launch
 import org.joda.time.LocalDate
 import timber.log.Timber
 import java.util.Date
+import javax.inject.Inject
 
-class SubmissionViewModel : ViewModel() {
+class SubmissionViewModel @Inject constructor() : CWAViewModel() {
     private val _scanStatus = MutableLiveData(Event(ScanStatus.STARTED))
 
     private val _registrationState = MutableLiveData(Event(ApiRequestState.IDLE))
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/TracingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/TracingViewModel.kt
index 06e3b5123..8e347e234 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/TracingViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/TracingViewModel.kt
@@ -3,7 +3,6 @@ package de.rki.coronawarnapp.ui.viewmodel
 import android.view.View
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MediatorLiveData
-import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import de.rki.coronawarnapp.CoronaWarnApplication
 import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL
@@ -19,13 +18,14 @@ import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction
 import de.rki.coronawarnapp.transaction.RiskLevelTransaction
 import de.rki.coronawarnapp.ui.riskdetails.DefaultRiskDetailPresenter
 import de.rki.coronawarnapp.util.ConnectivityHelper
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import kotlinx.coroutines.launch
 import org.joda.time.DateTime
 import org.joda.time.DateTimeZone
 import org.joda.time.Instant
 import timber.log.Timber
 import java.util.Date
+import javax.inject.Inject
 
 /**
  * Provides all the relevant data for tracing relevant topics and settings.
@@ -36,7 +36,7 @@ import java.util.Date
  * @see TracingRepository
  * @see RiskLevelRepository
  */
-class TracingViewModel : ViewModel() {
+class TracingViewModel @Inject constructor() : CWAViewModel() {
 
     companion object {
         val TAG: String? = TracingViewModel::class.simpleName
@@ -63,9 +63,6 @@ class TracingViewModel : ViewModel() {
     val additionalInformationVisibility = MediatorLiveData<Int>()
     val informationBodyNoticeVisibility = MediatorLiveData<Int>()
 
-    // event for interoperability navigation
-    val navigateToInteroperability = SingleLiveEvent<Boolean>()
-
     init {
         additionalInformationVisibility.addSource(riskLevel) {
             additionalInformationVisibility.value =
@@ -244,8 +241,4 @@ class TracingViewModel : ViewModel() {
     fun refreshLastSuccessfullyCalculatedScore() {
         RiskLevelRepository.refreshLastSuccessfullyCalculatedScore()
     }
-
-    fun onInteroperabilitySettingPressed() {
-        navigateToInteroperability.postValue(true)
-    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt
index fb3171270..e7a295a16 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt
@@ -1,6 +1,6 @@
 package de.rki.coronawarnapp.util
 
-import android.app.Activity
+import android.content.Context
 import android.text.SpannableString
 import android.text.method.LinkMovementMethod
 import android.text.util.Linkify
@@ -12,7 +12,7 @@ import java.util.regex.Pattern
 object DialogHelper {
 
     data class DialogInstance(
-        val activity: Activity,
+        val context: Context,
         val title: String,
         val message: String?,
         val positiveButton: String,
@@ -22,7 +22,7 @@ object DialogHelper {
         val negativeButtonFunction: () -> Unit? = {}
     ) {
         constructor(
-            activity: Activity,
+            context: Context,
             title: Int,
             message: Int,
             positiveButton: Int,
@@ -31,18 +31,18 @@ object DialogHelper {
             positiveButtonFunction: () -> Unit? = {},
             negativeButtonFunction: () -> Unit? = {}
         ) : this(
-            activity,
-            activity.resources.getString(title),
-            activity.resources.getString(message),
-            activity.resources.getString(positiveButton),
-            negativeButton?.let { activity.resources.getString(it) },
+            context,
+            context.resources.getString(title),
+            context.resources.getString(message),
+            context.resources.getString(positiveButton),
+            negativeButton?.let { context.resources.getString(it) },
             cancelable,
             positiveButtonFunction,
             negativeButtonFunction
         )
 
         constructor(
-            activity: Activity,
+            context: Context,
             title: Int,
             message: String,
             positiveButton: Int,
@@ -51,11 +51,11 @@ object DialogHelper {
             positiveButtonFunction: () -> Unit? = {},
             negativeButtonFunction: () -> Unit? = {}
         ) : this(
-            activity,
-            activity.resources.getString(title),
+            context,
+            context.resources.getString(title),
             message,
-            activity.resources.getString(positiveButton),
-            negativeButton?.let { activity.resources.getString(it) },
+            context.resources.getString(positiveButton),
+            negativeButton?.let { context.resources.getString(it) },
             cancelable,
             positiveButtonFunction,
             negativeButtonFunction
@@ -65,8 +65,8 @@ object DialogHelper {
     fun showDialog(
         dialogInstance: DialogInstance
     ): AlertDialog {
-        val message = getMessage(dialogInstance.activity, dialogInstance.message)
-        val alertDialog: AlertDialog = dialogInstance.activity.let {
+        val message = getMessage(dialogInstance.context, dialogInstance.message)
+        val alertDialog: AlertDialog = dialogInstance.context.let {
             val builder = AlertDialog.Builder(it)
             builder.apply {
                 setTitle(dialogInstance.title)
@@ -91,22 +91,22 @@ object DialogHelper {
         return alertDialog
     }
 
-    private fun getMessage(activity: Activity, message: String?): TextView {
+    private fun getMessage(context: Context, message: String?): TextView {
         // create spannable and add links, removed stack trace links into nowhere
         val spannable = SpannableString(message)
         val httpPattern: Pattern = Pattern.compile("[a-z]+://[^ \\n]*")
         Linkify.addLinks(spannable, httpPattern, "")
         // get padding for all sides
-        val paddingStartEnd = activity.resources.getDimension(R.dimen.spacing_normal).toInt()
-        val paddingLeftRight = activity.resources.getDimension(R.dimen.spacing_small).toInt()
+        val paddingStartEnd = context.resources.getDimension(R.dimen.spacing_normal).toInt()
+        val paddingLeftRight = context.resources.getDimension(R.dimen.spacing_small).toInt()
         // create a textview with clickable links from the spannable
-        val textView = TextView(activity)
+        val textView = TextView(context)
         textView.text = spannable
         textView.linksClickable = true
         textView.movementMethod = LinkMovementMethod.getInstance()
         textView.setPadding(paddingStartEnd, paddingLeftRight, paddingStartEnd, paddingLeftRight)
         textView.setTextAppearance(R.style.body1)
-        textView.setLinkTextColor(activity.getColorStateList(R.color.button_primary))
+        textView.setLinkTextColor(context.getColorStateList(R.color.button_primary))
         return textView
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt
index d2e5aa97c..93ad141a6 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt
@@ -7,6 +7,7 @@ import androidx.lifecycle.ProcessLifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction
+import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.worker.BackgroundWorkHelper
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import kotlinx.coroutines.launch
@@ -16,7 +17,9 @@ import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
-class WatchdogService @Inject constructor(private val context: Context) {
+class WatchdogService @Inject constructor(
+    @AppContext private val context: Context
+) {
 
     private val powerManager by lazy {
         context.getSystemService(Context.POWER_SERVICE) as PowerManager
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/DefaultPowerManagement.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/DefaultPowerManagement.kt
index 12475b333..9cdd2143a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/DefaultPowerManagement.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/DefaultPowerManagement.kt
@@ -5,10 +5,11 @@ import android.content.Intent
 import android.net.Uri
 import android.os.PowerManager
 import android.provider.Settings
+import de.rki.coronawarnapp.util.di.AppContext
 import javax.inject.Inject
 
 class DefaultPowerManagement @Inject constructor(
-    private val context: Context
+    @AppContext private val context: Context
 ) : PowerManagement {
 
     private val powerManager by lazy {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
index 15b4f6a39..b5e1e3a24 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
@@ -16,5 +16,6 @@ class AndroidModule {
 
     @Provides
     @Singleton
+    @AppContext
     fun context(app: Application): Context = app.applicationContext
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppContext.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppContext.kt
new file mode 100644
index 000000000..62a9e570e
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AppContext.kt
@@ -0,0 +1,8 @@
+package de.rki.coronawarnapp.util.di
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AppContext
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactory.kt
index 34bd1d355..a352abdb1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactory.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptedPreferencesFactory.kt
@@ -5,6 +5,7 @@ import android.content.SharedPreferences
 import androidx.security.crypto.EncryptedSharedPreferences
 import androidx.security.crypto.MasterKeys
 import de.rki.coronawarnapp.util.RetryMechanism
+import de.rki.coronawarnapp.util.di.AppContext
 import timber.log.Timber
 import java.security.KeyException
 import javax.inject.Inject
@@ -12,7 +13,7 @@ import javax.inject.Singleton
 
 @Singleton
 class EncryptedPreferencesFactory @Inject constructor(
-    private val context: Context
+    @AppContext private val context: Context
 ) {
 
     private val masterKeyAlias by lazy {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt
index de29ea8f6..329cff723 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/EncryptionErrorResetTool.kt
@@ -4,6 +4,7 @@ import android.content.Context
 import android.content.SharedPreferences
 import androidx.core.content.edit
 import de.rki.coronawarnapp.storage.DATABASE_NAME
+import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.util.errors.causes
 import org.joda.time.Instant
 import timber.log.Timber
@@ -22,7 +23,7 @@ import javax.inject.Singleton
  */
 @Singleton
 class EncryptionErrorResetTool @Inject constructor(
-    private val context: Context
+    @AppContext private val context: Context
 ) {
     // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ContextImpl.java;drc=3b8e8d76315f6718a982d5e6a019b3aa4f634bcd;l=626
     private val encryptedPreferencesFile by lazy {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt
new file mode 100644
index 000000000..50e4175de
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt
@@ -0,0 +1,8 @@
+package de.rki.coronawarnapp.util.ui
+
+import androidx.fragment.app.Fragment
+import androidx.navigation.NavDirections
+import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.ui.doNavigate
+
+fun Fragment.doNavigate(direction: NavDirections) = findNavController().doNavigate(direction)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt
index cdd903d65..d598cdf5b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt
@@ -11,12 +11,16 @@ import kotlin.reflect.KProperty
 
 fun <T : Any> ViewModel.smartLiveData(
     dispatcher: CoroutineDispatcher = Dispatchers.Default,
+    liveDataFactory: (ViewModel, CoroutineDispatcher) -> SmartLiveData<T> = { vm, disp ->
+        SmartLiveData(vm, disp)
+    },
     initAction: suspend () -> T
-) = SmartLiveDataProperty(dispatcher, initAction)
+) = SmartLiveDataProperty(dispatcher, initAction, liveDataFactory)
 
-class SmartLiveDataProperty<T : Any>(
+class SmartLiveDataProperty<T : Any, LV : SmartLiveData<T>>(
     private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
-    private val initialValueProvider: suspend () -> T
+    private val initialValueProvider: suspend () -> T,
+    private val liveDataFactory: (ViewModel, CoroutineDispatcher) -> LV
 ) : ReadOnlyProperty<ViewModel, SmartLiveData<T>> {
 
     private var liveData: SmartLiveData<T>? = null
@@ -29,7 +33,7 @@ class SmartLiveDataProperty<T : Any>(
             return@getValue it
         }
 
-        return SmartLiveData<T>(thisRef, dispatcher).also {
+        return liveDataFactory(thisRef, dispatcher).also {
             liveData = it
             thisRef.viewModelScope.launch(context = dispatcher) {
                 it.postValue(initialValueProvider())
@@ -38,7 +42,7 @@ class SmartLiveDataProperty<T : Any>(
     }
 }
 
-class SmartLiveData<T : Any>(
+open class SmartLiveData<T : Any>(
     private val viewModel: ViewModel,
     private val dispatcher: CoroutineDispatcher
 ) : MutableLiveData<T>() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt
index deaf4368c..9a61f091a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt
@@ -3,20 +3,37 @@ package de.rki.coronawarnapp.util.viewmodel
 import androidx.annotation.CallSuper
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.Dispatchers
+import de.rki.coronawarnapp.util.coroutine.DefaultDispatcherProvider
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 import timber.log.Timber
+import kotlin.coroutines.CoroutineContext
 
-abstract class CWAViewModel : ViewModel() {
+abstract class CWAViewModel constructor(
+    private val dispatcherProvider: DispatcherProvider = DefaultDispatcherProvider(),
+    private val childViewModels: List<CWAViewModel> = emptyList()
+) : ViewModel() {
+
+    private val tag: String = this::class.simpleName!!
 
     init {
-        Timber.v("Initialized")
+        Timber.tag(tag).v("Initialized")
     }
 
+    fun launch(
+        context: CoroutineContext = dispatcherProvider.Main,
+        block: suspend CoroutineScope.() -> Unit
+    ): Job = viewModelScope.launch(context = context, block = block)
+
     @CallSuper
     override fun onCleared() {
-        viewModelScope.launch(context = Dispatchers.Default) { }
-        Timber.v("onCleared()")
+        Timber.tag(tag).v("onCleared()")
+        childViewModels.forEach {
+            Timber.tag(tag).v("Clearing child VM: %s", it)
+            it.onCleared()
+        }
         super.onCleared()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/VerificationModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/VerificationModule.kt
index 8bd41f974..85d2bea56 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/VerificationModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/verification/VerificationModule.kt
@@ -7,6 +7,7 @@ import dagger.Reusable
 import de.rki.coronawarnapp.environment.verification.VerificationCDNServerUrl
 import de.rki.coronawarnapp.http.HttpClientDefault
 import de.rki.coronawarnapp.http.RestrictedConnectionSpecs
+import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.verification.server.VerificationApiV1
 import de.rki.coronawarnapp.verification.server.VerificationHttpClient
 import okhttp3.Cache
@@ -32,7 +33,7 @@ class VerificationModule {
     @Singleton
     @Provides
     fun provideVerificationApi(
-        context: Context,
+        @AppContext context: Context,
         @VerificationHttpClient client: OkHttpClient,
         @VerificationCDNServerUrl url: String,
         gsonConverterFactory: GsonConverterFactory
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_main.xml b/Corona-Warn-App/src/main/res/layout/fragment_home.xml
similarity index 99%
rename from Corona-Warn-App/src/main/res/layout/fragment_main.xml
rename to Corona-Warn-App/src/main/res/layout/fragment_home.xml
index 67452ab83..d0330cb93 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_main.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_home.xml
@@ -30,7 +30,7 @@
         android:layout_height="match_parent"
         android:contentDescription="@string/main_title"
         android:fillViewport="true"
-        tools:context="de.rki.coronawarnapp.ui.main.MainFragment">
+        tools:context="de.rki.coronawarnapp.ui.main.home.HomeFragment">
 
         <!-- todo apply merge tags through xml when applicable (eod) -->
         <androidx.constraintlayout.widget.ConstraintLayout
diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
index ac0bff96c..0ad00127e 100644
--- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
@@ -10,9 +10,9 @@
     <!-- Main -->
     <fragment
         android:id="@+id/mainFragment"
-        android:name="de.rki.coronawarnapp.ui.main.MainFragment"
+        android:name="de.rki.coronawarnapp.ui.main.home.HomeFragment"
         android:label="MainFragment"
-        tools:layout="@layout/fragment_main">
+        tools:layout="@layout/fragment_home">
         <action
             android:id="@+id/action_mainFragment_to_settingsTracingFragment"
             app:destination="@id/settingsTracingFragment" />
@@ -50,13 +50,13 @@
 
     <fragment
         android:id="@+id/mainSharingFragment"
-        android:name="de.rki.coronawarnapp.ui.main.MainShareFragment"
+        android:name="de.rki.coronawarnapp.ui.main.share.MainShareFragment"
         android:label="@layout/fragment_main_share"
         tools:layout="@layout/fragment_main_share" />
 
     <fragment
         android:id="@+id/mainOverviewFragment"
-        android:name="de.rki.coronawarnapp.ui.main.MainOverviewFragment"
+        android:name="de.rki.coronawarnapp.ui.main.overview.MainOverviewFragment"
         android:label="@layout/fragment_main_overview"
         tools:layout="@layout/fragment_main_overview" />
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/main/home/MainFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/main/home/MainFragmentViewModelTest.kt
new file mode 100644
index 000000000..4d2f97fd4
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/main/home/MainFragmentViewModelTest.kt
@@ -0,0 +1,2 @@
+package de.rki.coronawarnapp.ui.main.home
+
diff --git a/Corona-Warn-App/src/test/java/testhelpers/BaseTest.kt b/Corona-Warn-App/src/testShared/java/testhelpers/BaseTest.kt
similarity index 100%
rename from Corona-Warn-App/src/test/java/testhelpers/BaseTest.kt
rename to Corona-Warn-App/src/testShared/java/testhelpers/BaseTest.kt
diff --git a/Corona-Warn-App/src/test/java/testhelpers/logging/JUnitTree.kt b/Corona-Warn-App/src/testShared/java/testhelpers/logging/JUnitTree.kt
similarity index 100%
rename from Corona-Warn-App/src/test/java/testhelpers/logging/JUnitTree.kt
rename to Corona-Warn-App/src/testShared/java/testhelpers/logging/JUnitTree.kt
-- 
GitLab