diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle
index e7fda93ad22d7261d4db870b6dd3ad44bdeaccf0..fe696278452c74c83edf5b794619dc26d090a8f4 100644
--- a/Corona-Warn-App/build.gradle
+++ b/Corona-Warn-App/build.gradle
@@ -307,7 +307,7 @@ dependencies {
     def nav_version = "2.3.3"
     implementation 'androidx.appcompat:appcompat:1.2.0'
     implementation 'androidx.core:core-ktx:1.3.2'
-    implementation 'com.google.android.material:material:1.2.1'
+    implementation 'com.google.android.material:material:1.3.0'
     implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
     implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
     implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
@@ -382,7 +382,6 @@ dependencies {
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
     androidTestImplementation 'androidx.test:rules:1.3.0'
     androidTestImplementation 'androidx.test.ext:truth:1.3.0'
-    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
     androidTestImplementation 'androidx.work:work-testing:2.5.0'
     androidTestImplementation "io.mockk:mockk-android:1.10.4"
     debugImplementation 'androidx.fragment:fragment-testing:1.2.5'
diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json
new file mode 100644
index 0000000000000000000000000000000000000000..f5c14553bb62386c785fe0f4f551332968cc8ee0
--- /dev/null
+++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json
@@ -0,0 +1,180 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "48d71b0a00d0c7fe01ffe05d5fecb512",
+    "entities": [
+      {
+        "tableName": "checkin",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `guid` TEXT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `traceLocationStart` TEXT, `traceLocationEnd` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `signature` TEXT NOT NULL, `checkInStart` TEXT NOT NULL, `checkInEnd` TEXT, `targetCheckInEnd` TEXT, `createJournalEntry` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "guid",
+            "columnName": "guid",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "version",
+            "columnName": "version",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "traceLocationStart",
+            "columnName": "traceLocationStart",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "traceLocationEnd",
+            "columnName": "traceLocationEnd",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "defaultCheckInLengthInMinutes",
+            "columnName": "defaultCheckInLengthInMinutes",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "signature",
+            "columnName": "signature",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "checkInStart",
+            "columnName": "checkInStart",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "checkInEnd",
+            "columnName": "checkInEnd",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "targetCheckInEnd",
+            "columnName": "targetCheckInEnd",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "createJournalEntry",
+            "columnName": "createJournalEntry",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "traceLocations",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`guid` TEXT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `signature` TEXT NOT NULL, PRIMARY KEY(`guid`))",
+        "fields": [
+          {
+            "fieldPath": "guid",
+            "columnName": "guid",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "version",
+            "columnName": "version",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "startDate",
+            "columnName": "startDate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "endDate",
+            "columnName": "endDate",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "defaultCheckInLengthInMinutes",
+            "columnName": "defaultCheckInLengthInMinutes",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "signature",
+            "columnName": "signature",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "guid"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '48d71b0a00d0c7fe01ffe05d5fecb512')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5e837010548de435714ef67edef1c885c00c4b56
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifierTest.kt
@@ -0,0 +1,111 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+import de.rki.coronawarnapp.environment.EnvironmentSetup
+import de.rki.coronawarnapp.eventregistration.common.decodeBase32
+import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
+import de.rki.coronawarnapp.util.security.SignatureValidation
+import io.kotest.assertions.throwables.shouldNotThrowAny
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import kotlinx.coroutines.test.runBlockingTest
+import org.joda.time.Instant
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import testhelpers.BaseTestInstrumentation
+
+@RunWith(JUnit4::class)
+@Ignore("FIXME: Provide new encoded signed trace location samples")
+class DefaultQRCodeVerifierTest : BaseTestInstrumentation() {
+
+    @MockK lateinit var environmentSetup: EnvironmentSetup
+    private lateinit var qrCodeVerifier: QRCodeVerifier
+
+    @Before
+    fun setUp() {
+        MockKAnnotations.init(this)
+        every { environmentSetup.appConfigVerificationKey } returns PUB_KEY
+        qrCodeVerifier = DefaultQRCodeVerifier(SignatureValidation(environmentSetup))
+    }
+
+    @Test
+    fun verifyEventSuccess() = runBlockingTest {
+        val time = 2687960 * 1_000L
+        val instant = Instant.ofEpochMilli(time)
+        shouldNotThrowAny {
+            val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT)
+            verifyResult.apply {
+                singedTraceLocation.location.description shouldBe "CWA Launch Party"
+                verifyResult.isBeforeStartTime(instant) shouldBe false
+                verifyResult.isAfterEndTime(instant) shouldBe false
+            }
+        }
+    }
+
+    @Test
+    fun verifyEventStartTimeWaning() = runBlockingTest {
+        val time = 2687940 * 1_000L
+        val instant = Instant.ofEpochMilli(time)
+        shouldNotThrowAny {
+            val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT)
+            verifyResult.apply {
+                singedTraceLocation.location.description shouldBe "CWA Launch Party"
+            }
+            verifyResult.isBeforeStartTime(instant) shouldBe true
+            verifyResult.isAfterEndTime(instant) shouldBe false
+        }
+    }
+
+    @Test
+    fun verifyEventEndTimeWarning() = runBlockingTest {
+        val instant = Instant.now()
+        shouldNotThrowAny {
+            val verifyResult = qrCodeVerifier.verify(ENCODED_EVENT)
+            verifyResult.apply {
+                singedTraceLocation.location.description shouldBe "CWA Launch Party"
+            }
+            verifyResult.isBeforeStartTime(instant) shouldBe false
+            verifyResult.isAfterEndTime(instant) shouldBe true
+        }
+    }
+
+    @Test
+    fun verifyEventWithInvalidKey() = runBlockingTest {
+        every { environmentSetup.appConfigVerificationKey } returns INVALID_PUB_KEY
+        shouldThrow<InvalidQRCodeSignatureException> {
+            qrCodeVerifier.verify(ENCODED_EVENT)
+        }
+    }
+
+    @Test
+    fun eventHasMalformedData() = runBlockingTest {
+        shouldThrow<InvalidQRCodeDataException> {
+            qrCodeVerifier.verify(INVALID_ENCODED_EVENT)
+        }
+    }
+
+    companion object {
+
+        private const val INVALID_PUB_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg" +
+            "3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg=="
+
+        private const val PUB_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEafIKZOiRPuJWjKOUmKv7OTJWTyii" +
+            "4oCQLcGn3FgYoLQaJIvAM3Pl7anFDPPY/jxfqqrLyGc0f6hWQ9JPR3QjBw=="
+
+        private const val INVALID_ENCODED_EVENT =
+            "BIPEY33SMVWSA2LQON2W2IDEN5WG64RAONUXIIDBNVSXILBAMNXRBCM4UQARRKM6UQASAHRKCC7CTDWGQ" +
+                "4JCO7RVZSWVIMQK4UPA.GBCAEIA7TEORBTUA25QHBOCWT26BCA5PORBS2E4FFWMJ3U" +
+                "U3P6SXOL7SHUBCA7UEZBDDQ2R6VRJH7WBJKVF7GZYJA6YMRN27IPEP7NKGGJSWX3XQ"
+
+        private const val ENCODED_EVENT =
+            "BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBOJ2" +
+                "HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGC" +
+                "PUZ2RQACAYEJ3HQYMAFFBU2SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU" +
+                "7TYERH23B746RQTABO3CTI="
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2797a59ee206d546406dc7f7e460bb2839dfe756
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt
@@ -0,0 +1,40 @@
+package de.rki.coronawarnapp.eventregistration.storage
+
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity
+import org.joda.time.Instant
+
+object CheckInDatabaseData {
+
+    val testCheckIn = TraceLocationCheckInEntity(
+        guid = "testGuid1",
+        version = 1,
+        type = TraceLocation.Type.TEMPORARY_OTHER.value,
+        description = "testDescription1",
+        address = "testAddress1",
+        traceLocationStart = Instant.parse("2021-01-01T12:00:00.000Z"),
+        traceLocationEnd = Instant.parse("2021-01-01T15:00:00.000Z"),
+        defaultCheckInLengthInMinutes = 15,
+        signature = "Signature",
+        checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"),
+        checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z"),
+        targetCheckInEnd = Instant.parse("2021-01-01T12:45:00.000Z"),
+        createJournalEntry = true
+    )
+
+    val testCheckInWithoutCheckOutTime = TraceLocationCheckInEntity(
+        guid = "testGuid2",
+        version = 1,
+        type = TraceLocation.Type.TEMPORARY_OTHER.value,
+        description = "testDescription2",
+        address = "testAddress2",
+        traceLocationStart = null,
+        traceLocationEnd = null,
+        defaultCheckInLengthInMinutes = null,
+        signature = "Signature",
+        checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"),
+        checkInEnd = null,
+        targetCheckInEnd = Instant.parse("2021-01-01T12:45:00.000Z"),
+        createJournalEntry = true
+    )
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..60223bea5e51e2caba73607eb59fa01dabca3b01
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt
@@ -0,0 +1,85 @@
+package de.rki.coronawarnapp.eventregistration.storage
+
+import androidx.room.Room
+import androidx.test.core.app.ApplicationProvider
+import de.rki.coronawarnapp.eventregistration.storage.CheckInDatabaseData.testCheckIn
+import de.rki.coronawarnapp.eventregistration.storage.CheckInDatabaseData.testCheckInWithoutCheckOutTime
+import io.kotest.matchers.shouldBe
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.joda.time.Instant
+import org.junit.After
+import org.junit.Test
+import testhelpers.BaseTestInstrumentation
+
+class TraceLocationCheckInDaoTest : BaseTestInstrumentation() {
+
+    private val traceLocationDatabase = Room.inMemoryDatabaseBuilder(
+        ApplicationProvider.getApplicationContext(),
+        TraceLocationDatabase::class.java
+    ).build()
+
+    private val checkInDao = traceLocationDatabase.eventCheckInDao()
+
+    @After
+    fun tearDown() {
+        traceLocationDatabase.clearAllTables()
+    }
+
+    @Test
+    fun traceLocationCheckInDaoShouldReturnNoEntriesInitially() = runBlocking {
+        val checkInsFlow = checkInDao.allEntries()
+
+        checkInsFlow.first() shouldBe emptyList()
+    }
+
+    @Test
+    fun traceLocationCheckInDaoShouldSuccessfullyInsertCheckIn() = runBlocking {
+        val checkInsFlow = checkInDao.allEntries()
+
+        val generatedId = checkInDao.insert(testCheckIn)
+
+        checkInsFlow.first() shouldBe listOf(testCheckIn.copy(id = generatedId))
+    }
+
+    @Test
+    fun traceLocationCheckInDaoShouldSuccessfullyInsertMultipleCheckIns() = runBlocking {
+        val checkInsFlow = checkInDao.allEntries()
+
+        val testCheckInGeneratedId = checkInDao.insert(testCheckIn)
+        val testCheckInWithoutCheckOutTimeGeneratedId = checkInDao.insert(testCheckInWithoutCheckOutTime)
+
+        checkInsFlow.first() shouldBe listOf(
+            testCheckIn.copy(id = testCheckInGeneratedId),
+            testCheckInWithoutCheckOutTime.copy(id = testCheckInWithoutCheckOutTimeGeneratedId)
+        )
+    }
+
+    @Test
+    fun traceLocationCheckInDaoShouldSuccessfullyUpdateCheckIn() = runBlocking {
+        val checkInsFlow = checkInDao.allEntries()
+
+        val testCheckInGeneratedId = checkInDao.insert(testCheckInWithoutCheckOutTime)
+
+        val updatedCheckIn = testCheckInWithoutCheckOutTime.copy(
+            id = testCheckInGeneratedId,
+            checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z")
+        )
+
+        checkInDao.update(updatedCheckIn)
+
+        checkInsFlow.first() shouldBe listOf(updatedCheckIn)
+    }
+
+    @Test
+    fun traceLocationCheckInDaoShouldSuccessfullyDeleteAllCheckIns() = runBlocking {
+        val checkInsFlow = checkInDao.allEntries()
+
+        checkInDao.insert(testCheckIn)
+        checkInDao.insert(testCheckInWithoutCheckOutTime)
+
+        checkInDao.deleteAll()
+
+        checkInsFlow.first() shouldBe emptyList()
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDaoTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDaoTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ab29881320b63e7230c4aea57350bbe554ba4dad
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDaoTest.kt
@@ -0,0 +1,72 @@
+package de.rki.coronawarnapp.eventregistration.storage
+
+import androidx.room.Room
+import androidx.test.core.app.ApplicationProvider
+import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabaseData.testTraceLocation1
+import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabaseData.testTraceLocation2
+import io.kotest.matchers.shouldBe
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Test
+import testhelpers.BaseTestInstrumentation
+
+class TraceLocationDaoTest : BaseTestInstrumentation() {
+
+    private val traceLocationDatabase = Room.inMemoryDatabaseBuilder(
+        ApplicationProvider.getApplicationContext(),
+        TraceLocationDatabase::class.java
+    ).build()
+
+    private val traceLocationDao = traceLocationDatabase.traceLocationDao()
+
+    @After
+    fun tearDown() {
+        traceLocationDatabase.clearAllTables()
+    }
+
+    @Test
+    fun traceLocationDaoShouldReturnNoEntriesInitially() = runBlocking {
+        val traceLocationsFlow = traceLocationDao.allEntries()
+
+        traceLocationsFlow.first() shouldBe emptyList()
+    }
+
+    @Test
+    fun traceLocationDaoShouldSuccessfullyInsertTraceLocation() = runBlocking {
+        val traceLocationsFlow = traceLocationDao.allEntries()
+
+        traceLocationDao.insert(testTraceLocation1)
+
+        traceLocationsFlow.first() shouldBe listOf(testTraceLocation1)
+    }
+
+    @Test
+    fun traceLocationDaoShouldSuccessfullyInsertMultipleTraceLocations() = runBlocking {
+        val traceLocationsFlow = traceLocationDao.allEntries()
+
+        traceLocationDao.insert(testTraceLocation1)
+        traceLocationDao.insert(testTraceLocation2)
+
+        traceLocationsFlow.first() shouldBe listOf(testTraceLocation1, testTraceLocation2)
+    }
+
+    @Test
+    fun traceLocationDaoShouldSuccessfullyDeleteSingleTraceLocation() = runBlocking {
+        val traceLocationsFlow = traceLocationDao.allEntries()
+
+        traceLocationDao.insert(testTraceLocation1)
+        traceLocationDao.delete(testTraceLocation1)
+        traceLocationsFlow.first() shouldBe emptyList()
+    }
+
+    @Test
+    fun traceLocationDaoShouldSuccessfullyDeleteAllTraceLocations() = runBlocking {
+        val traceLocationsFlow = traceLocationDao.allEntries()
+
+        traceLocationDao.insert(testTraceLocation1)
+        traceLocationDao.insert(testTraceLocation2)
+        traceLocationDao.deleteAll()
+        traceLocationsFlow.first() shouldBe emptyList()
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabaseData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabaseData.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3891b6bdfd0e2d397f6208a0381a67b9ae360cce
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabaseData.kt
@@ -0,0 +1,32 @@
+package de.rki.coronawarnapp.eventregistration.storage
+
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity
+import org.joda.time.Instant
+
+object TraceLocationDatabaseData {
+
+    val testTraceLocation1 = TraceLocationEntity(
+        guid = "TestGuid1",
+        version = 1,
+        type = TraceLocation.Type.TEMPORARY_OTHER,
+        description = "TestTraceLocation1",
+        address = "TestTraceLocationAddress1",
+        startDate = Instant.parse("2021-01-01T12:00:00.000Z"),
+        endDate = Instant.parse("2021-01-01T18:00:00.000Z"),
+        defaultCheckInLengthInMinutes = null,
+        signature = "signature1"
+    )
+
+    val testTraceLocation2 = TraceLocationEntity(
+        guid = "TestGuid2",
+        version = 1,
+        type = TraceLocation.Type.PERMANENT_OTHER,
+        description = "TestTraceLocation2",
+        address = "TestTraceLocationAddress2",
+        startDate = null,
+        endDate = null,
+        defaultCheckInLengthInMinutes = 15,
+        signature = "signature2"
+    )
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocationKtTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocationKtTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9935f9eed60890c9939c8cc860ee6cf9f603e5ef
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocationKtTest.kt
@@ -0,0 +1,44 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin
+
+import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult
+import de.rki.coronawarnapp.eventregistration.common.decodeBase32
+import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
+import io.kotest.assertions.throwables.shouldNotThrowAny
+import io.kotest.matchers.shouldBe
+import org.joda.time.Instant
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import testhelpers.BaseTestInstrumentation
+
+@RunWith(JUnit4::class)
+@Ignore("FIXME: Provide new encoded signed trace location samples")
+class VerifiedTraceLocationKtTest : BaseTestInstrumentation() {
+
+    @Test
+    fun testVerifiedTraceLocationMapping() {
+        shouldNotThrowAny {
+            val signedTraceLocation =
+                TraceLocationOuterClass.SignedTraceLocation.parseFrom(
+                    DECODED_TRACE_LOCATION.decodeBase32().toByteArray()
+                )
+            val verifiedTraceLocation =
+                QRCodeVerifyResult(singedTraceLocation = signedTraceLocation).toVerifiedTraceLocation()
+            verifiedTraceLocation shouldBe VerifiedTraceLocation(
+                guid = "Yc48RFi/hfyXKlF4DEDs/w==",
+                start = Instant.parse("1970-02-01T02:39:15.000Z"),
+                end = Instant.parse("1970-02-01T02:39:51.000Z"),
+                defaultCheckInLengthInMinutes = 30,
+                description = "CWA Launch Party"
+            )
+        }
+    }
+
+    companion object {
+        private const val DECODED_TRACE_LOCATION =
+            "BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBOJ2HSGGTQ6SACIHXQ6SAC" +
+                "KA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFFBU2SQCEEAJAUCJSQJ7WDM6" +
+                "75MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI="
+    }
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9d6924d1d165d12308513254e14a127f9e88793f
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityTest.kt
@@ -0,0 +1,98 @@
+package de.rki.coronawarnapp.ui.launcher
+
+import android.content.Intent
+import android.net.Uri
+import androidx.test.core.app.launchActivity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.main.CWASettings
+import de.rki.coronawarnapp.storage.LocalData
+import de.rki.coronawarnapp.update.UpdateChecker
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.spyk
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+import testhelpers.TestDispatcherProvider
+
+@RunWith(AndroidJUnit4::class)
+class LauncherActivityTest : BaseUITest() {
+
+    @MockK lateinit var updateChecker: UpdateChecker
+    @MockK lateinit var cwaSettings: CWASettings
+    lateinit var viewModel: LauncherActivityViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this)
+        mockkObject(LocalData)
+        coEvery { updateChecker.checkForUpdate() } returns UpdateChecker.Result(isUpdateNeeded = false)
+        every { LocalData.isOnboarded() } returns false
+        viewModel = launcherActivityViewModel()
+        setupMockViewModel(
+            object : LauncherActivityViewModel.Factory {
+                override fun create(): LauncherActivityViewModel = viewModel
+            }
+        )
+
+        every { viewModel.events } returns mockk<SingleLiveEvent<LauncherEvent>>().apply {
+            every { observe(any(), any()) } just Runs
+        }
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun testDeepLinkLowercase() {
+        val uri = Uri.parse("https://e.coronawarn.app/c1/SOME_PATH_GOES_HERE")
+        launchActivity<LauncherActivity>(getIntent(uri))
+    }
+
+    @Test(expected = RuntimeException::class)
+    fun testDeepLinkDoNotOpenOtherLinks() {
+        val uri = Uri.parse("https://www.rki.de")
+        launchActivity<LauncherActivity>(getIntent(uri))
+    }
+
+    @Test(expected = RuntimeException::class)
+    fun testDeepLinkUppercase() {
+        // Host is case sensitive and it should be only in lowercase
+        val uri = Uri.parse("HTTPS://CORONAWARN.APP/E1/SOME_PATH_GOES_HERE")
+        launchActivity<LauncherActivity>(getIntent(uri))
+    }
+
+    private fun getIntent(uri: Uri) = Intent(Intent.ACTION_VIEW, uri).apply {
+        setPackage(InstrumentationRegistry.getInstrumentation().targetContext.packageName)
+        addCategory(Intent.CATEGORY_BROWSABLE)
+        addCategory(Intent.CATEGORY_DEFAULT)
+    }
+
+    private fun launcherActivityViewModel() = spyk(
+        LauncherActivityViewModel(
+            updateChecker,
+            TestDispatcherProvider(),
+            cwaSettings
+        )
+    )
+}
+
+@Module
+abstract class LauncherActivityTestModule {
+    @ContributesAndroidInjector
+    abstract fun launcherActivity(): LauncherActivity
+}
diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt
index 203a4d16b69a8e91d3845c315eb96b28eaf30e67..bafd923f25f69ba70ec07f32dba3d6c26cd72c6e 100644
--- a/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt
+++ b/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt
@@ -4,6 +4,7 @@ import dagger.BindsInstance
 import dagger.Component
 import dagger.android.AndroidInjector
 import dagger.android.support.AndroidSupportInjectionModule
+import de.rki.coronawarnapp.ui.launcher.LauncherActivityTestModule
 import testhelpers.viewmodels.MockViewModelModule
 import javax.inject.Singleton
 
@@ -13,6 +14,7 @@ import javax.inject.Singleton
         MockViewModelModule::class,
         FragmentTestModuleRegistrar::class,
         TestAndroidModule::class,
+        LauncherActivityTestModule::class,
     ]
 )
 @Singleton
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7ccb3476a11a996eb1c0d6e3bf04f54b4b1c9646
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt
@@ -0,0 +1,61 @@
+package de.rki.coronawarnapp.test.eventregistration.ui
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentTestEventregistrationBinding
+import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.doNavigate
+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
+
+@SuppressLint("SetTextI18n")
+class EventRegistrationTestFragment : Fragment(R.layout.fragment_test_eventregistration), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val viewModel: EventRegistrationTestFragmentViewModel by cwaViewModels { viewModelFactory }
+
+    private val binding: FragmentTestEventregistrationBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        with(binding) {
+            scanCheckInQrCode.setOnClickListener {
+                doNavigate(
+                    EventRegistrationTestFragmentDirections
+                        .actionEventRegistrationTestFragmentToScanCheckInQrCodeFragment()
+                )
+            }
+
+            testQrCodeCreation.setOnClickListener {
+                doNavigate(
+                    EventRegistrationTestFragmentDirections
+                        .actionEventRegistrationTestFragmentToTestQrCodeCreationFragment()
+                )
+            }
+
+            createEventButton.setOnClickListener {
+                findNavController().navigate(R.id.createEventTestFragment)
+            }
+
+            showEventsButton.setOnClickListener {
+                findNavController().navigate(R.id.showStoredEventsTestFragment)
+            }
+        }
+    }
+
+    companion object {
+        val MENU_ITEM = TestMenuItem(
+            title = "Event Registration",
+            description = "View & Control the event registration.",
+            targetId = R.id.eventRegistrationTestFragment
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3c95a0a4a47d729182eca300cc9e5b7a70ad655f
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.test.eventregistration.ui
+
+import dagger.Binds
+import dagger.Module
+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 EventRegistrationTestFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(EventRegistrationTestFragmentViewModel::class)
+    abstract fun testEventRegistrationFragment(
+        factory: EventRegistrationTestFragmentViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..105a19bd4b837e72457a9be0033ae564a7a785e7
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentViewModel.kt
@@ -0,0 +1,15 @@
+package de.rki.coronawarnapp.test.eventregistration.ui
+
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class EventRegistrationTestFragmentViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<EventRegistrationTestFragmentViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/PrintingAdapter.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/PrintingAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2d20c87a1d4b6f1d63abc4e59f62c813740ff26a
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/PrintingAdapter.kt
@@ -0,0 +1,75 @@
+package de.rki.coronawarnapp.test.eventregistration.ui
+
+import android.os.Bundle
+import android.os.CancellationSignal
+import android.os.ParcelFileDescriptor
+import android.print.PageRange
+import android.print.PrintAttributes
+import android.print.PrintDocumentAdapter
+import android.print.PrintDocumentInfo
+import timber.log.Timber
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+
+/**
+ * Printing adapter for poster PDF files
+ */
+class PrintingAdapter(
+    private val file: File
+) : PrintDocumentAdapter() {
+
+    override fun onLayout(
+        oldAttributes: PrintAttributes?,
+        newAttributes: PrintAttributes?,
+        cancellationSignal: CancellationSignal,
+        callback: LayoutResultCallback,
+        extras: Bundle?
+    ) {
+        if (cancellationSignal.isCanceled) {
+            callback.onLayoutCancelled()
+            Timber.i("onLayoutCancelled")
+            return
+        }
+
+        val info = PrintDocumentInfo.Builder(file.name)
+            .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+            .setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+            .build()
+        Timber.i(
+            "onLayoutFinished(info:%s, oldAttributes:%s, newAttributes:%s)",
+            info,
+            oldAttributes,
+            newAttributes
+        )
+        callback.onLayoutFinished(info, oldAttributes != newAttributes)
+    }
+
+    override fun onWrite(
+        pages: Array<out PageRange>,
+        destination: ParcelFileDescriptor,
+        cancellationSignal: CancellationSignal,
+        callback: WriteResultCallback
+    ) = try {
+        FileInputStream(file).use { input ->
+            FileOutputStream(destination.fileDescriptor).use { output ->
+                val bytesCopied = input.copyTo(output)
+                Timber.i("bytesCopied:$bytesCopied")
+            }
+        }
+
+        when {
+            cancellationSignal.isCanceled -> {
+                Timber.i("onWriteCancelled")
+                callback.onWriteCancelled()
+            }
+            else -> {
+                Timber.i("onWriteFinished")
+                callback.onWriteFinished(arrayOf(PageRange.ALL_PAGES))
+            }
+        }
+    } catch (e: Exception) {
+        callback.onWriteFailed(e.message)
+        Timber.e(e, "Printing $file failed")
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3e6daef237373951dc50bf59d214b44f3b72232b
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragment.kt
@@ -0,0 +1,88 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.createevent
+
+import android.os.Bundle
+import android.view.View
+import android.widget.ArrayAdapter
+import android.widget.AutoCompleteTextView
+import androidx.core.widget.doAfterTextChanged
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.contactdiary.util.hideKeyboard
+import de.rki.coronawarnapp.databinding.FragmentTestCreateeventBinding
+import de.rki.coronawarnapp.util.di.AutoInject
+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 timber.log.Timber
+import javax.inject.Inject
+
+class CreateEventTestFragment : Fragment(R.layout.fragment_test_createevent), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: CreateEventTestViewModel by cwaViewModels { viewModelFactory }
+
+    private val binding: FragmentTestCreateeventBinding by viewBindingLazy()
+
+    private val eventString = "Event"
+    private val locationString = "Location"
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        initSpinner()
+        initOnCreateEventClicked()
+        observeViewModelResult()
+    }
+
+    private fun observeViewModelResult() {
+        vm.result.observe2(this) {
+            when (it) {
+                is CreateEventTestViewModel.Result.Success ->
+                    binding.resultText.text = "Successfully stored: ${it.eventEntity}"
+                is CreateEventTestViewModel.Result.Error ->
+                    binding.resultText.text = "There is something wrong with your input values, please check again."
+            }
+        }
+    }
+
+    private fun initOnCreateEventClicked() = with(binding) {
+        createEventButton.setOnClickListener {
+            vm.createEvent(
+                eventOrLocationSpinner.editText!!.text.toString(),
+                eventDescription.text.toString(),
+                eventAddress.text.toString(),
+                eventStartEditText.text.toString(),
+                eventEndEditText.text.toString(),
+                eventDefaultCheckinLengthInMinutes.text.toString()
+            )
+            it.hideKeyboard()
+        }
+    }
+
+    private fun initSpinner() {
+        val items = listOf(eventString, locationString)
+        with(binding.eventOrLocationSpinner.editText as AutoCompleteTextView) {
+            setText(items.first(), false)
+            setAdapter(ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, items))
+            doAfterTextChanged { }
+            doOnTextChanged { text, start, before, count ->
+                Timber.d("text: $text, start: $start, before: $before, count: $count")
+
+                when (text.toString()) {
+                    eventString -> {
+                        binding.eventStart.visibility = View.VISIBLE
+                        binding.eventEnd.visibility = View.VISIBLE
+                    }
+                    locationString -> {
+                        binding.eventStart.visibility = View.GONE
+                        binding.eventEnd.visibility = View.GONE
+                        binding.eventStartEditText.text = null
+                        binding.eventEndEditText.text = null
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..258a96c7ad463471d8cac73695fcb3e546f3ae5a
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragmentModule.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.createevent
+
+import dagger.Binds
+import dagger.Module
+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 CreateEventTestFragmentModule {
+
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(CreateEventTestViewModel::class)
+    abstract fun testCreateEventFragment(
+        factory: CreateEventTestViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3668a469297caae2efb4ecde3da605eb74d23d8e
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestViewModel.kt
@@ -0,0 +1,75 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.createevent
+
+import androidx.lifecycle.MutableLiveData
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.eventregistration.events.DefaultTraceLocation
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import org.joda.time.DateTime
+import org.joda.time.format.DateTimeFormat
+import timber.log.Timber
+import java.util.UUID
+
+class CreateEventTestViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider,
+    private val traceLocationRepository: TraceLocationRepository
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<CreateEventTestViewModel>
+
+    val result = MutableLiveData<Result>()
+
+    fun createEvent(
+        type: String,
+        description: String,
+        address: String,
+        start: String,
+        end: String,
+        defaultCheckInLengthInMinutes: String
+    ) {
+        try {
+            val startDate =
+                if (start.isBlank()) null else DateTime.parse(start, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"))
+            val endDate =
+                if (end.isBlank()) null else DateTime.parse(end, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"))
+
+            /* TODO: wait for new protobuf messages 'TraceLocation' and perform network request to get
+                'SignedTraceLocation' */
+
+            // Backend needs UNIX timestamp in Seconds, not milliseconds
+            val startTimeStampSeconds = startDate?.toInstant()?.seconds ?: 0
+            val endTimeStampSeconds = endDate?.toInstant()?.seconds ?: 0
+
+            val traceLocationType =
+                if (type == "Event") TraceLocation.Type.TEMPORARY_OTHER else TraceLocation.Type.PERMANENT_OTHER
+
+            val traceLocation = DefaultTraceLocation(
+                UUID.randomUUID().toString(), // will be provided by the server when the endpoint is ready
+                traceLocationType,
+                description,
+                address,
+                startDate?.toInstant(),
+                endDate?.toInstant(),
+                defaultCheckInLengthInMinutes.toInt(),
+                "ServerSignature"
+            )
+
+            traceLocationRepository.addTraceLocation(traceLocation)
+            result.postValue(Result.Success(traceLocation))
+        } catch (exception: Exception) {
+            Timber.d("Something went wrong when trying to create an event: $exception")
+            result.postValue(Result.Error)
+        }
+    }
+
+    sealed class Result {
+        object Error : Result()
+        data class Success(val eventEntity: TraceLocation) : Result()
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..abd5b1c1be9461567ee0dbf1fccc308797c2bb08
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt
@@ -0,0 +1,84 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.qrcode
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.print.PrintAttributes
+import android.print.PrintManager
+import android.view.View
+import android.widget.Toast
+import androidx.core.content.getSystemService
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentTestQrcodeCreationBinding
+import de.rki.coronawarnapp.test.eventregistration.ui.PrintingAdapter
+import de.rki.coronawarnapp.util.di.AutoInject
+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 timber.log.Timber
+import javax.inject.Inject
+
+class QrCodeCreationTestFragment : Fragment(R.layout.fragment_test_qrcode_creation), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+
+    private val viewModel: QrCodeCreationTestViewModel by cwaViewModels { viewModelFactory }
+    private val binding: FragmentTestQrcodeCreationBinding by viewBindingLazy()
+
+    @SuppressLint("SetTextI18n")
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+
+        viewModel.sharingIntent.observe2(this) { fileIntent ->
+
+            binding.printPDF.isVisible = true
+            binding.printPDF.setOnClickListener {
+                // Context must be an Activity context
+                val printingManger = context?.getSystemService<PrintManager>()
+                Timber.i("PrintingManager: $printingManger")
+                printingManger?.apply {
+                    val printingJob = print(
+                        "CoronaWarnApp",
+                        PrintingAdapter(fileIntent.file),
+                        PrintAttributes
+                            .Builder()
+                            .setMediaSize(PrintAttributes.MediaSize.ISO_A3)
+                            .build()
+                    )
+
+                    Timber.i("PrintingJob:$printingJob")
+                    Timber.i("PrintingJob isBlocked:${printingJob.isBlocked}")
+                    Timber.i("PrintingJob isCancelled:${printingJob.isCancelled}")
+                    Timber.i("PrintingJob isCompleted:${printingJob.isCompleted}")
+                    Timber.i("PrintingJob isFailed:${printingJob.isFailed}")
+                    Timber.i("PrintingJob info:${printingJob.info}")
+                }
+            }
+            binding.sharePDF.isVisible = true
+            binding.sharePDF.setOnClickListener {
+                startActivity(fileIntent.intent(requireActivity()))
+            }
+        }
+
+        viewModel.qrCodeBitmap.observe2(this) {
+            binding.qrCodeImage.setImageBitmap(it)
+            if (it != null) {
+                viewModel.createPDF(binding.pdfPage)
+            }
+        }
+
+        viewModel.errorMessage.observe2(this) {
+            Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show()
+        }
+
+        binding.qrCodeText.setText(
+            "HTTPS://E.CORONAWARN.APP/C1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUD" +
+                "BOJ2HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFF" +
+                "BU2SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI="
+        )
+        binding.generateQrCode.setOnClickListener {
+            viewModel.createQrCode(binding.qrCodeText.text.toString())
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..33fa4c48d9b99cc6df1ef0c3801310a0dccda239
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragmentModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.qrcode
+
+import dagger.Binds
+import dagger.Module
+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 QrCodeCreationTestFragmentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(QrCodeCreationTestViewModel::class)
+    abstract fun qrCodeCreation(
+        factory: QrCodeCreationTestViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6dd59f2e683ef8ba88781a3711a673e369c31b65
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt
@@ -0,0 +1,124 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.qrcode
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Color.BLACK
+import android.graphics.Color.WHITE
+import android.graphics.pdf.PdfDocument
+import android.view.View
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.EncodeHintType
+import com.google.zxing.MultiFormatWriter
+import com.google.zxing.common.BitMatrix
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.di.AppContext
+import de.rki.coronawarnapp.util.files.FileSharing
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import timber.log.Timber
+import java.io.File
+import java.io.FileOutputStream
+
+class QrCodeCreationTestViewModel @AssistedInject constructor(
+    private val dispatcher: DispatcherProvider,
+    private val fileSharing: FileSharing,
+    @AppContext private val context: Context
+) : CWAViewModel(dispatcher) {
+
+    val qrCodeBitmap = SingleLiveEvent<Bitmap>()
+    val errorMessage = SingleLiveEvent<String>()
+    val sharingIntent = SingleLiveEvent<FileSharing.FileIntentProvider>()
+
+    /**
+     * Creates a QR Code [Bitmap] ,result is delivered by [qrCodeBitmap]
+     */
+    fun createQrCode(input: String) = launch(context = dispatcher.IO) {
+        qrCodeBitmap.postValue(encodeAsBitmap(input))
+    }
+
+    /**
+     * Create a new PDF file and result is delivered by [sharingIntent]
+     * as a sharing [FileSharing.ShareIntentProvider]
+     */
+    fun createPDF(
+        view: View
+    ) = launch(context = dispatcher.IO) {
+        try {
+            val file = pdfFile()
+            val pageInfo = PdfDocument.PageInfo.Builder(
+                view.width,
+                view.height,
+                1
+            ).create()
+
+            PdfDocument().apply {
+                startPage(pageInfo).apply {
+                    view.draw(canvas)
+                    finishPage(this)
+                }
+
+                FileOutputStream(file).use {
+                    writeTo(it)
+                    close()
+                }
+            }
+
+            sharingIntent.postValue(
+                fileSharing.getFileIntentProvider(file, "Scan and Help")
+            )
+        } catch (e: Exception) {
+            errorMessage.postValue(e.localizedMessage ?: "Creating pdf failed")
+            Timber.d(e, "Creating pdf failed")
+        }
+    }
+
+    private fun pdfFile(): File {
+        val dir = File(context.filesDir, "events")
+        if (!dir.exists()) dir.mkdirs()
+        return File(dir, "CoronaWarnApp-Event.pdf")
+    }
+
+    private fun encodeAsBitmap(input: String, size: Int = 1000): Bitmap? {
+        return try {
+            val hints = mapOf(
+                EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H
+                // This is not required in the specs and it should not be enabled
+                // it is causing crash on older Android versions ex:API 23
+                // EncodeHintType.CHARACTER_SET to Charsets.UTF_8
+            )
+            MultiFormatWriter().encode(
+                input,
+                BarcodeFormat.QR_CODE,
+                size,
+                size,
+                hints
+            ).toBitmap()
+        } catch (e: Exception) {
+            Timber.d(e, "Qr code creation failed")
+            errorMessage.postValue(e.localizedMessage ?: "QR code creation failed")
+            null
+        }
+    }
+
+    private fun BitMatrix.toBitmap() =
+        Bitmap.createBitmap(
+            context.resources.displayMetrics,
+            width,
+            height,
+            Bitmap.Config.ARGB_8888
+        ).apply {
+            for (x in 0 until width) {
+                for (y in 0 until height) {
+                    val color = if (get(x, y)) BLACK else WHITE
+                    setPixel(x, y, color)
+                }
+            }
+        }
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<QrCodeCreationTestViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..62bd7b04808a57d947b01fc3693ff8b70acdbbf4
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragment.kt
@@ -0,0 +1,48 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.showevents
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentTestShowstoredeventsBinding
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import de.rki.coronawarnapp.util.di.AutoInject
+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
+
+class ShowStoredEventsTestFragment : Fragment(R.layout.fragment_test_showstoredevents), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: ShowStoredEventsTestViewModel by cwaViewModels { viewModelFactory }
+
+    private val binding: FragmentTestShowstoredeventsBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        vm.storedEvents.observe2(this) { events ->
+            binding.storedEvents.text = events.joinToString(separator = "\n\n") { it.getSimpleUIString() }
+        }
+
+        binding.deleteAllEvents.setOnClickListener {
+            vm.deleteAllEvents()
+        }
+    }
+
+    private fun TraceLocation.getSimpleUIString(): String {
+        return listOf(
+            "guid = $guid",
+            "type = $type",
+            "description = $description",
+            "location = $address",
+            "startTime = $startDate",
+            "endTime = $endDate",
+            "defaultCheckInLengthInMinutes = $defaultCheckInLengthInMinutes",
+            "signature = $signature",
+            "version = $version"
+        ).joinToString(separator = "\n")
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragmentModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..45e3c4763e437a8cc30a17c144e77d7e1af7174c
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragmentModule.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.showevents
+
+import dagger.Binds
+import dagger.Module
+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 ShowStoredEventsTestFragmentModule {
+
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(ShowStoredEventsTestViewModel::class)
+    abstract fun testStoredEventsFragment(
+        factory: ShowStoredEventsTestViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f85513da0a4ec166c8df5c5e13973a0b7a2acfc5
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestViewModel.kt
@@ -0,0 +1,24 @@
+package de.rki.coronawarnapp.test.eventregistration.ui.showevents
+
+import androidx.lifecycle.asLiveData
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class ShowStoredEventsTestViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider,
+    private val traceLocationRepository: TraceLocationRepository
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<ShowStoredEventsTestViewModel>
+
+    val storedEvents = traceLocationRepository.allTraceLocations.asLiveData()
+
+    fun deleteAllEvents() {
+        traceLocationRepository.deleteAllTraceLocations()
+    }
+}
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt
index 6d09d4e6bc5d38de5e6556b177aa116a3a37b415..e631969b4290fe76b22c48870f35bb55b96d1b9d 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt
@@ -10,6 +10,7 @@ import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment
 import de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragment
 import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment
 import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment
+import de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragment
 import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment
 import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment
 import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment
@@ -34,7 +35,8 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() {
             ContactDiaryTestFragment.MENU_ITEM,
             PlaygroundFragment.MENU_ITEM,
             DataDonationTestFragment.MENU_ITEM,
-            DeltaonboardingFragment.MENU_ITEM
+            DeltaonboardingFragment.MENU_ITEM,
+            EventRegistrationTestFragment.MENU_ITEM,
         ).let { MutableLiveData(it) }
     }
     val showTestScreenEvent = SingleLiveEvent<TestMenuItem>()
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
index da9c43ddb5f631cfcd805aea75a1852960dd46ff..e0ecef22020975d24885f70fd2549a8b96656083 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt
@@ -14,6 +14,14 @@ import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment
 import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragmentModule
 import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaOnboardingFragmentModule
 import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment
+import de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragment
+import de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragmentModule
+import de.rki.coronawarnapp.test.eventregistration.ui.createevent.CreateEventTestFragment
+import de.rki.coronawarnapp.test.eventregistration.ui.createevent.CreateEventTestFragmentModule
+import de.rki.coronawarnapp.test.eventregistration.ui.qrcode.QrCodeCreationTestFragment
+import de.rki.coronawarnapp.test.eventregistration.ui.qrcode.QrCodeCreationTestFragmentModule
+import de.rki.coronawarnapp.test.eventregistration.ui.showevents.ShowStoredEventsTestFragment
+import de.rki.coronawarnapp.test.eventregistration.ui.showevents.ShowStoredEventsTestFragmentModule
 import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment
 import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragmentModule
 import de.rki.coronawarnapp.test.menu.ui.TestMenuFragment
@@ -65,4 +73,16 @@ abstract class MainActivityTestModule {
 
     @ContributesAndroidInjector(modules = [DeltaOnboardingFragmentModule::class])
     abstract fun deltaOnboarding(): DeltaonboardingFragment
+
+    @ContributesAndroidInjector(modules = [EventRegistrationTestFragmentModule::class])
+    abstract fun eventRegistration(): EventRegistrationTestFragment
+
+    @ContributesAndroidInjector(modules = [QrCodeCreationTestFragmentModule::class])
+    abstract fun qrCodeCreation(): QrCodeCreationTestFragment
+
+    @ContributesAndroidInjector(modules = [CreateEventTestFragmentModule::class])
+    abstract fun createEvent(): CreateEventTestFragment
+
+    @ContributesAndroidInjector(modules = [ShowStoredEventsTestFragmentModule::class])
+    abstract fun showStoredEvents(): ShowStoredEventsTestFragment
 }
diff --git a/Corona-Warn-App/src/deviceForTesters/res/drawable/ic_bug.xml b/Corona-Warn-App/src/deviceForTesters/res/drawable/ic_bug.xml
index ed88658407baec3e8107d6281f279e9d0af2f86c..a9c970bdc30479e734b9bdaf08b5c9cf631e74ca 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/drawable/ic_bug.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/drawable/ic_bug.xml
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
     android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
     android:viewportWidth="24"
     android:viewportHeight="24">
     <path
diff --git a/Corona-Warn-App/src/deviceForTesters/res/drawable/ic_coffee.xml b/Corona-Warn-App/src/deviceForTesters/res/drawable/ic_coffee.xml
index 95c65dbf1b3c2232925dc54ff1f98bc6e1c49567..d7fde78a86ca740f295e828894aa3c5f16ea3e7d 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/drawable/ic_coffee.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/drawable/ic_coffee.xml
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
     android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
     android:viewportWidth="24"
     android:viewportHeight="24">
     <path
diff --git a/Corona-Warn-App/src/deviceForTesters/res/drawable/qr_code_print_template.png b/Corona-Warn-App/src/deviceForTesters/res/drawable/qr_code_print_template.png
new file mode 100644
index 0000000000000000000000000000000000000000..70ce653f099e59942922b03275beac482038fe04
Binary files /dev/null and b/Corona-Warn-App/src/deviceForTesters/res/drawable/qr_code_print_template.png differ
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_createevent.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_createevent.xml
new file mode 100644
index 0000000000000000000000000000000000000000..afd82d5c5bb5f5f2b149d587eae2549a9a3204ac
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_createevent.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="@dimen/spacing_normal">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                style="@style/headline5"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/spacing_normal"
+                android:text="Create new event"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:ignore="HardcodedText" />
+
+            <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/eventOrLocationSpinner"
+                style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
+                android:layout_marginBottom="@dimen/spacing_tiny"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="Type">
+
+                <AutoCompleteTextView
+                    android:enabled="false"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:inputType="none" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <com.google.android.material.textfield.TextInputLayout
+                style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/spacing_tiny">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/event_description"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="Description"
+                    android:maxLines="1"
+                    android:padding="@dimen/spacing_tiny"
+                    tools:ignore="HardcodedText" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <com.google.android.material.textfield.TextInputLayout
+                style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/spacing_tiny">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/event_address"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="Address"
+                    android:maxLines="1"
+                    android:padding="@dimen/spacing_tiny"
+                    tools:ignore="HardcodedText" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/event_start"
+                style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/spacing_tiny">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/event_start_edit_text"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="Start (yyyy-MM-dd HH:mm)"
+                    android:maxLines="1"
+                    android:padding="@dimen/spacing_tiny"
+                    tools:ignore="HardcodedText" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/event_end"
+                style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/spacing_tiny">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/event_end_edit_text"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="End (yyyy-MM-dd HH:mm)"
+                    android:maxLines="1"
+                    android:padding="@dimen/spacing_tiny"
+                    tools:ignore="HardcodedText" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <com.google.android.material.textfield.TextInputLayout
+                style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+
+                android:layout_marginBottom="@dimen/spacing_normal">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/event_default_checkin_length_in_minutes"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="Default Check-In Lenght in Minutes - multiple of 10"
+                    android:maxLines="1"
+                    android:padding="@dimen/spacing_tiny"
+                    android:inputType="numberDecimal"
+                    tools:ignore="HardcodedText" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/create_event_button"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Create Event"
+                tools:ignore="HardcodedText" />
+
+            <TextView
+                android:id="@+id/resultText"
+                style="@style/subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/spacing_normal"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:ignore="HardcodedText" />
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml
new file mode 100644
index 0000000000000000000000000000000000000000..390245bc42225eea7e9109b18285697cd8bcb503
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    tools:ignore="HardcodedText">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:orientation="vertical"
+        android:paddingBottom="32dp">
+
+        <LinearLayout
+            style="@style/Card"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/spacing_tiny"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="QRCode, PDF event registration" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/testQrCodeCreation"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:text="QR Code Creation" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            style="@style/Card"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/spacing_tiny"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:orientation="vertical">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="QRCode scanning" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/scanCheckInQrCode"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scan check in QR code" />
+
+        </LinearLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/event_container"
+            style="@style/Card"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/spacing_tiny">
+
+            <TextView
+                android:id="@+id/event_title"
+                style="@style/headline6"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="8dp"
+                android:text="Events"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/events_body"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/spacing_tiny"
+                android:textIsSelectable="true"
+                android:text="After creating an event in the app, it is sent to the server and returned together with a guid and a signature."
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/event_title" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/create_event_button"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:layout_marginEnd="8dp"
+                android:text="Create Event"
+                app:layout_constraintEnd_toStartOf="@+id/show_events_button"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/events_body" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/show_events_button"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:text="Show stored Events"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/create_event_button"
+                app:layout_constraintTop_toBottomOf="@id/events_body" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+    </LinearLayout>
+</androidx.core.widget.NestedScrollView>
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6ae5f29730ea651f8a8e8bc0d61f010427562518
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fillViewport="true"
+    tools:ignore="HardcodedText">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/generateQrCode"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp"
+            android:text="QR Code"
+            app:layout_constraintEnd_toStartOf="@+id/sharePDF"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/sharePDF"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp"
+            android:text="Share PDF"
+            android:visibility="invisible"
+            app:layout_constraintEnd_toStartOf="@+id/printPDF"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toEndOf="@+id/generateQrCode"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/printPDF"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp"
+            android:text="Print PDF"
+            android:visibility="invisible"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toEndOf="@+id/sharePDF"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/qrCodeTextLayout"
+            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/generateQrCode">
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/qrCodeText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textSize="14sp" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/pdfPage"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/qrCodeTextLayout">
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/pdfTemplateImageView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:adjustViewBounds="true"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:srcCompat="@drawable/qr_code_print_template" />
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/qrCodeImage"
+                android:layout_width="250dp"
+                android:layout_height="250dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="@id/pdfTemplateImageView"
+                app:layout_constraintVertical_bias="0.25" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</ScrollView>
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_showstoredevents.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_showstoredevents.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3521aafdae703228d9a98da5f2297dfdb2ef9a14
--- /dev/null
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_showstoredevents.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="@dimen/spacing_normal">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/storedEvents"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/spacing_normal"
+                android:text="Show stored Events"
+                tools:ignore="HardcodedText" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/deleteAllEvents"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Delete All Events" />
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
index d4b7265dbf3cd6f7b563669a33c8f4319319a8b4..5f3d417bb90e61a607e9fb9a7950d147a8b2f30f 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
@@ -46,6 +46,9 @@
         <action
             android:id="@+id/action_test_menu_fragment_to_deltaonboardingFragment"
             app:destination="@id/test_deltaonboarding_fragment" />
+        <action
+            android:id="@+id/action_test_menu_fragment_to_eventRegistrationTestFragment"
+            app:destination="@id/eventRegistrationTestFragment" />
     </fragment>
 
     <fragment
@@ -124,5 +127,46 @@
         android:name="de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment"
         android:label="DeltaonboardingFragment"
         tools:layout="@layout/fragment_test_deltaonboarding" />
+    <fragment
+        android:id="@+id/eventRegistrationTestFragment"
+        android:name="de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragment"
+        android:label="EventRegistrationTestFragment"
+        tools:layout="@layout/fragment_test_eventregistration">
+        <action
+            android:id="@+id/action_eventRegistrationTestFragment_to_test_qr_code_creation_fragment"
+            app:destination="@id/test_qr_code_creation_fragment" />
+        <action
+            android:id="@+id/action_eventRegistrationTestFragment_to_scanCheckInQrCodeFragment"
+            app:destination="@id/scanCheckInQrCodeFragmentTest" />
+        <action
+            android:id="@+id/action_eventRegistrationTestFragment_to_CreateEventTestFragment"
+            app:destination="@id/createEventTestFragment" />
+        <action
+            android:id="@+id/action_eventRegistrationTestFragment_to_ShowStoredEventsTestFragment"
+            app:destination="@id/showStoredEventsTestFragment" />
+    </fragment>
+
+    <fragment
+        android:id="@+id/test_qr_code_creation_fragment"
+        android:name="de.rki.coronawarnapp.test.eventregistration.ui.qrcode.QrCodeCreationTestFragment"
+        android:label="QrCodeCreationTestFragment"
+        tools:layout="@layout/fragment_test_qrcode_creation" />
+    <fragment
+        android:id="@+id/scanCheckInQrCodeFragmentTest"
+        android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeFragment"
+        android:label="ScanCheckInQrCodeFragment"
+        tools:layout="@layout/fragment_submission_qr_code_scan" />
+
+    <fragment
+        android:id="@+id/createEventTestFragment"
+        android:name="de.rki.coronawarnapp.test.eventregistration.ui.createevent.CreateEventTestFragment"
+        android:label="CreateEventTestFragment"
+        tools:layout="@layout/fragment_test_createevent" />
+    <fragment
+        android:id="@+id/showStoredEventsTestFragment"
+        android:name="de.rki.coronawarnapp.test.eventregistration.ui.showevents.ShowStoredEventsTestFragment"
+        android:label="ShowStoredEventsTestFragment"
+        tools:layout="@layout/fragment_test_showstoredevents" />
+
 
 </navigation>
diff --git a/Corona-Warn-App/src/main/AndroidManifest.xml b/Corona-Warn-App/src/main/AndroidManifest.xml
index 8c7a9babcd15f82d582dc03215616e6d6047bcc0..2a68d8aca15fb7ea6f131f893d7c19a70979691d 100644
--- a/Corona-Warn-App/src/main/AndroidManifest.xml
+++ b/Corona-Warn-App/src/main/AndroidManifest.xml
@@ -54,12 +54,25 @@
 
         <activity
             android:name=".ui.launcher.LauncherActivity"
+            android:exported="true"
             android:screenOrientation="portrait"
             android:theme="@style/AppTheme.Launcher">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data
+                    android:host="e.coronawarn.app"
+                    android:pathPrefix="/"
+                    android:scheme="https" />
+            </intent-filter>
         </activity>
         <activity
             android:name=".ui.main.MainActivity"
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2a082136720b9aaa1cc5007295b39be1345e57c3
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.eventregistration
+
+import dagger.Binds
+import dagger.Module
+import de.rki.coronawarnapp.eventregistration.checkins.qrcode.DefaultQRCodeVerifier
+import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier
+import de.rki.coronawarnapp.eventregistration.storage.repo.DefaultTraceLocationRepository
+import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
+
+@Module
+abstract class EventRegistrationModule {
+
+    @Binds
+    abstract fun qrCodeVerifier(qrCodeVerifier: DefaultQRCodeVerifier): QRCodeVerifier
+
+    @Binds
+    abstract fun traceLocationRepository(defaultTraceLocationRepo: DefaultTraceLocationRepository):
+        TraceLocationRepository
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ae36ed6417ba73682c5f2c598daf837978b74027
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt
@@ -0,0 +1,21 @@
+package de.rki.coronawarnapp.eventregistration.checkins
+
+import org.joda.time.Instant
+
+@Suppress("LongParameterList")
+class CheckIn(
+    val id: Long,
+    val guid: String,
+    val version: Int,
+    val type: Int,
+    val description: String,
+    val address: String,
+    val traceLocationStart: Instant?,
+    val traceLocationEnd: Instant?,
+    val defaultCheckInLengthInMinutes: Int?,
+    val signature: String,
+    val checkInStart: Instant,
+    val checkInEnd: Instant?,
+    val targetCheckInEnd: Instant?,
+    val createJournalEntry: Boolean
+)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..46c7905290df6b0c35482962cf2c90e5faa641b4
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt
@@ -0,0 +1,76 @@
+package de.rki.coronawarnapp.eventregistration.checkins
+
+import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase
+import de.rki.coronawarnapp.eventregistration.storage.dao.CheckInDao
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity
+import de.rki.coronawarnapp.util.coroutine.AppScope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+class CheckInRepository @Inject constructor(
+    traceLocationDatabaseFactory: TraceLocationDatabase.Factory,
+    @AppScope private val appScope: CoroutineScope
+) {
+
+    private val traceLocationDatabase: TraceLocationDatabase by lazy {
+        traceLocationDatabaseFactory.create()
+    }
+
+    private val checkInDao: CheckInDao by lazy {
+        traceLocationDatabase.eventCheckInDao()
+    }
+
+    val allCheckIns: Flow<List<CheckIn>> =
+        checkInDao
+            .allEntries()
+            .map { list -> list.map { it.toCheckIn() } }
+
+    fun addCheckIn(checkIn: CheckIn) {
+        appScope.launch {
+            checkInDao.insert(checkIn.toEntity())
+        }
+    }
+
+    fun updateCheckIn(checkIn: CheckIn) {
+        appScope.launch {
+            checkInDao.update(checkIn.toEntity())
+        }
+    }
+}
+
+private fun TraceLocationCheckInEntity.toCheckIn() = CheckIn(
+    id = id,
+    guid = guid,
+    version = version,
+    type = type,
+    description = description,
+    address = address,
+    traceLocationStart = traceLocationStart,
+    traceLocationEnd = traceLocationEnd,
+    defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes,
+    signature = signature,
+    checkInStart = checkInStart,
+    checkInEnd = checkInEnd,
+    targetCheckInEnd = targetCheckInEnd,
+    createJournalEntry = createJournalEntry
+)
+
+private fun CheckIn.toEntity() = TraceLocationCheckInEntity(
+    id = id,
+    guid = guid,
+    version = version,
+    type = type,
+    description = description,
+    address = address,
+    traceLocationStart = traceLocationStart,
+    traceLocationEnd = traceLocationEnd,
+    defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes,
+    signature = signature,
+    checkInStart = checkInStart,
+    checkInEnd = checkInEnd,
+    targetCheckInEnd = targetCheckInEnd,
+    createJournalEntry = createJournalEntry
+)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/download/CheckInsPackage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/download/CheckInsPackage.kt
new file mode 100644
index 0000000000000000000000000000000000000000..97f513181b57d32ee384dedf307409a778cf8adf
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/download/CheckInsPackage.kt
@@ -0,0 +1,11 @@
+package de.rki.coronawarnapp.eventregistration.checkins.download
+
+import de.rki.coronawarnapp.eventregistration.checkins.CheckIn
+
+interface CheckInsPackage {
+
+    /**
+     * Hides the file reading
+     */
+    suspend fun extractCheckIns(): List<CheckIn>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/download/DownloadedCheckInsRepo.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/download/DownloadedCheckInsRepo.kt
new file mode 100644
index 0000000000000000000000000000000000000000..748cee11448f772318c85beecd0fb9e1542fcb33
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/download/DownloadedCheckInsRepo.kt
@@ -0,0 +1,12 @@
+package de.rki.coronawarnapp.eventregistration.checkins.download
+
+import kotlinx.coroutines.flow.Flow
+
+interface DownloadedCheckInsRepo {
+
+    val allCheckInsPackages: Flow<List<CheckInsPackage>>
+
+    fun addCheckIns(checkins: List<CheckInsPackage>)
+
+    fun removeCheckIns(checkins: List<CheckInsPackage>)
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifier.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifier.kt
new file mode 100644
index 0000000000000000000000000000000000000000..db0a9559c20c59b0671a9bddb49cbd4dc1eac113
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultQRCodeVerifier.kt
@@ -0,0 +1,42 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+import de.rki.coronawarnapp.eventregistration.common.decodeBase32
+import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
+import de.rki.coronawarnapp.util.security.SignatureValidation
+import timber.log.Timber
+import javax.inject.Inject
+
+class DefaultQRCodeVerifier @Inject constructor(
+    private val signatureValidation: SignatureValidation
+) : QRCodeVerifier {
+
+    override suspend fun verify(encodedTraceLocation: String): QRCodeVerifyResult {
+        Timber.tag(TAG).v("Verifying: %s", encodedTraceLocation)
+
+        val signedTraceLocation = try {
+            TraceLocationOuterClass.SignedTraceLocation.parseFrom(encodedTraceLocation.decodeBase32().toByteArray())
+        } catch (e: Exception) {
+            throw InvalidQRCodeDataException(cause = e, message = "QR-code data could not be parsed.")
+        }
+        Timber.tag(TAG).d("Parsed to signed location: %s", signedTraceLocation)
+
+        val isValid = try {
+            signatureValidation.hasValidSignature(
+                signedTraceLocation.location.toByteArray(),
+                sequenceOf(signedTraceLocation.signature.toByteArray())
+            )
+        } catch (e: Exception) {
+            throw InvalidQRCodeDataException(cause = e, message = "Verification failed.")
+        }
+
+        if (!isValid) {
+            throw InvalidQRCodeSignatureException(message = "QR-code did not match signature.")
+        }
+
+        return QRCodeVerifyResult(signedTraceLocation)
+    }
+
+    companion object {
+        private const val TAG = "DefaultQRCodeVerifier"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeDataException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeDataException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..935e86b319ff3fc5ca3d685371c417fe706bf64a
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeDataException.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+class InvalidQRCodeDataException constructor(
+    message: String? = null,
+    cause: Throwable? = null
+) : QRCodeException(message, cause)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeSignatureException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeSignatureException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..eae77689d0aab837f5527c2682e72f55078c70ab
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidQRCodeSignatureException.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+class InvalidQRCodeSignatureException constructor(
+    message: String? = null,
+    cause: Throwable? = null
+) : QRCodeException(message, cause)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1fb4c881a81819cd76149537923ad3956f8d942a
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeException.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+open class QRCodeException constructor(
+    message: String? = null,
+    cause: Throwable? = null
+) : Exception(message, cause)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifier.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifier.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8e7e2452d58b283c699c89cc3864c3e517457636
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifier.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+interface QRCodeVerifier {
+
+    suspend fun verify(encodedEvent: String): QRCodeVerifyResult
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifyResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifyResult.kt
new file mode 100644
index 0000000000000000000000000000000000000000..24b3a2430321fbcf9788b8cc5eea8556d2cfcc59
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/QRCodeVerifyResult.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds
+import org.joda.time.Instant
+
+data class QRCodeVerifyResult(
+    val singedTraceLocation: TraceLocationOuterClass.SignedTraceLocation
+) {
+    fun isBeforeStartTime(now: Instant): Boolean {
+        val startTimestamp = singedTraceLocation.location.startTimestamp
+        return startTimestamp != 0L && startTimestamp > now.seconds
+    }
+
+    fun isAfterEndTime(now: Instant): Boolean {
+        val endTimestamp = singedTraceLocation.location.endTimestamp
+        return endTimestamp != 0L && endTimestamp < now.seconds
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..48af21b14167f849d326fdcd9e1ccc5060dfacdb
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidator.kt
@@ -0,0 +1,24 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+import java.net.URI
+
+private const val SCHEME = "https"
+private const val AUTHORITY = "e.coronawarn.app"
+private const val PATH_PREFIX = "/c1"
+private const val SIGNED_TRACE_LOCATION_BASE_32_REGEX =
+    "^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?\$"
+
+/**
+ * Validate that QRCode scanned uri matches the following formulas:
+ * https://e.coronawarn.app/c1/SIGNED_TRACE_LOCATION_BASE32
+ * HTTPS://E.CORONAWARN.APP/C1/SIGNED_TRACE_LOCATION_BASE32
+ */
+fun String.isValidQRCodeUri(): Boolean =
+    URI.create(this).run {
+        scheme.equals(SCHEME, true) &&
+            authority.equals(AUTHORITY, true) &&
+            path.substringBeforeLast("/")
+                .equals(PATH_PREFIX, true) &&
+            path.substringAfterLast("/")
+                .matches(Regex(SIGNED_TRACE_LOCATION_BASE_32_REGEX))
+    }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/common/Base32.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/common/Base32.kt
new file mode 100644
index 0000000000000000000000000000000000000000..21af3649a21a400ec221cc3c0b8a60733457e4d0
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/common/Base32.kt
@@ -0,0 +1,30 @@
+package de.rki.coronawarnapp.eventregistration.common
+
+import com.google.common.io.BaseEncoding
+import okio.ByteString
+import okio.ByteString.Companion.toByteString
+
+/**
+ * Decodes [String] into [ByteString] using Base32 decoder
+ * @return [ByteString]
+ */
+fun String.decodeBase32(): ByteString = BaseEncoding.base32().decode(this).toByteString()
+
+/**
+ * Encodes [ByteString] into base32 [String]
+ * @return [String]
+ */
+fun ByteString.base32(padding: Boolean = true): String = when {
+    padding -> BaseEncoding.base32().encode(toByteArray())
+    else -> BaseEncoding.base32().omitPadding().encode(toByteArray())
+}
+
+/**
+ * Returns Base32 encoded string using [Charsets.UTF_8]
+ * @param padding [Boolean] true by default ,Output will have '=' characters padding
+ * @return [String]
+ */
+fun String.base32(padding: Boolean = true): String = when {
+    padding -> BaseEncoding.base32().encode(toByteArray(Charsets.UTF_8))
+    else -> BaseEncoding.base32().omitPadding().encode(toByteArray(Charsets.UTF_8))
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocation.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f792ebf9ea52339be007226803ba26598b307ce8
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocation.kt
@@ -0,0 +1,45 @@
+package de.rki.coronawarnapp.eventregistration.events
+
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity
+import org.joda.time.Instant
+
+const val TRACE_LOCATION_VERSION = 1
+
+data class DefaultTraceLocation(
+    override val guid: String,
+    override val type: TraceLocation.Type,
+    override val description: String,
+    override val address: String,
+    override val startDate: Instant?,
+    override val endDate: Instant?,
+    override val defaultCheckInLengthInMinutes: Int?,
+    override val signature: String,
+    override val version: Int = TRACE_LOCATION_VERSION,
+) : TraceLocation
+
+fun List<TraceLocationEntity>.toTraceLocations() = this.map { it.toTraceLocation() }
+
+fun TraceLocationEntity.toTraceLocation() = DefaultTraceLocation(
+    guid = guid,
+    type = type,
+    description = description,
+    address = address,
+    startDate = startDate,
+    endDate = endDate,
+    defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes,
+    signature = signature,
+    version = version
+)
+
+/*fun SignedEventOuterClass.SignedEvent.toHostedEvent(): TraceLocation =
+    DefaultTraceLocation(
+        guid = event.guid.toString(),
+        type = enumValues<TraceLocation.Type>()[type],
+        description = event.description,
+        address = "hardcodedLocation", // event.location,
+        // backend needs UNIX timestamp in seconds, so we have to multiply it by 1000 to get milliseconds
+        startDate = Instant.ofEpochMilli(event.start.toLong() * 1000),
+        endDate = Instant.ofEpochMilli(event.end.toLong() * 1000),
+        defaultCheckInLengthInMinutes = event.defaultCheckInLengthInMinutes,
+        signature = signature.toString()
+    )*/
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/TraceLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/TraceLocation.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1316ea7996b67e139cf70ffe404017bf91c1e23c
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/TraceLocation.kt
@@ -0,0 +1,21 @@
+package de.rki.coronawarnapp.eventregistration.events
+
+import org.joda.time.Instant
+
+interface TraceLocation {
+    val guid: String
+    val version: Int
+    val type: Type
+    val description: String
+    val address: String
+    val startDate: Instant?
+    val endDate: Instant?
+    val defaultCheckInLengthInMinutes: Int?
+    val signature: String
+
+    enum class Type(val value: Int) {
+        UNSPECIFIED(0),
+        PERMANENT_OTHER(1),
+        TEMPORARY_OTHER(2)
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt
new file mode 100644
index 0000000000000000000000000000000000000000..30e508cf4c45d0a9145c9551e64523c457f20a99
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt
@@ -0,0 +1,38 @@
+package de.rki.coronawarnapp.eventregistration.storage
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import de.rki.coronawarnapp.eventregistration.storage.dao.CheckInDao
+import de.rki.coronawarnapp.eventregistration.storage.dao.TraceLocationDao
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationConverters
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity
+import de.rki.coronawarnapp.util.database.CommonConverters
+import de.rki.coronawarnapp.util.di.AppContext
+import javax.inject.Inject
+
+@Database(
+    entities = [
+        TraceLocationCheckInEntity::class,
+        TraceLocationEntity::class
+    ],
+    version = 1,
+    exportSchema = true
+)
+@TypeConverters(CommonConverters::class, TraceLocationConverters::class)
+abstract class TraceLocationDatabase : RoomDatabase() {
+
+    abstract fun eventCheckInDao(): CheckInDao
+    abstract fun traceLocationDao(): TraceLocationDao
+
+    class Factory @Inject constructor(@AppContext private val context: Context) {
+        fun create() = Room
+            .databaseBuilder(context, TraceLocationDatabase::class.java, TRACE_LOCATIONS_DATABASE_NAME)
+            .build()
+    }
+}
+
+private const val TRACE_LOCATIONS_DATABASE_NAME = "TraceLocations_db"
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt
new file mode 100644
index 0000000000000000000000000000000000000000..75f5cbd8987f0d8172c5217cc03154afe0b02984
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt
@@ -0,0 +1,24 @@
+package de.rki.coronawarnapp.eventregistration.storage.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.Update
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface CheckInDao {
+
+    @Query("SELECT * FROM checkin")
+    fun allEntries(): Flow<List<TraceLocationCheckInEntity>>
+
+    @Insert
+    suspend fun insert(entity: TraceLocationCheckInEntity): Long
+
+    @Update
+    suspend fun update(entity: TraceLocationCheckInEntity)
+
+    @Query("DELETE FROM checkin")
+    suspend fun deleteAll()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/TraceLocationDao.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/TraceLocationDao.kt
new file mode 100644
index 0000000000000000000000000000000000000000..888f5e3c4bc76f34eead4cf92d0ef1c10e72498b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/TraceLocationDao.kt
@@ -0,0 +1,25 @@
+package de.rki.coronawarnapp.eventregistration.storage.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+@Suppress("UnnecessaryAbstractClass")
+abstract class TraceLocationDao {
+
+    @Query("SELECT * FROM traceLocations")
+    abstract fun allEntries(): Flow<List<TraceLocationEntity>>
+
+    @Insert
+    abstract suspend fun insert(traceLocation: TraceLocationEntity)
+
+    @Delete
+    abstract suspend fun delete(traceLocation: TraceLocationEntity)
+
+    @Query("DELETE FROM traceLocations")
+    abstract suspend fun deleteAll()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..354715f9cf6227ef1043339731b60ad421a45227
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt
@@ -0,0 +1,24 @@
+package de.rki.coronawarnapp.eventregistration.storage.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import org.joda.time.Instant
+
+@Entity(tableName = "checkin")
+data class TraceLocationCheckInEntity(
+    @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Long = 0L,
+    @ColumnInfo(name = "guid") val guid: String,
+    @ColumnInfo(name = "version") val version: Int,
+    @ColumnInfo(name = "type") val type: Int,
+    @ColumnInfo(name = "description") val description: String,
+    @ColumnInfo(name = "address") val address: String,
+    @ColumnInfo(name = "traceLocationStart") val traceLocationStart: Instant?,
+    @ColumnInfo(name = "traceLocationEnd") val traceLocationEnd: Instant?,
+    @ColumnInfo(name = "defaultCheckInLengthInMinutes") val defaultCheckInLengthInMinutes: Int?,
+    @ColumnInfo(name = "signature") val signature: String,
+    @ColumnInfo(name = "checkInStart") val checkInStart: Instant,
+    @ColumnInfo(name = "checkInEnd") val checkInEnd: Instant?,
+    @ColumnInfo(name = "targetCheckInEnd") val targetCheckInEnd: Instant?,
+    @ColumnInfo(name = "createJournalEntry") val createJournalEntry: Boolean
+)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConverters.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConverters.kt
new file mode 100644
index 0000000000000000000000000000000000000000..89546329e0ea2ac472ad58100bb89d733ec818dd
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConverters.kt
@@ -0,0 +1,13 @@
+package de.rki.coronawarnapp.eventregistration.storage.entity
+
+import androidx.room.TypeConverter
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+
+class TraceLocationConverters {
+
+    @TypeConverter
+    fun toTraceLocationType(value: Int) = enumValues<TraceLocation.Type>().single { it.value == value }
+
+    @TypeConverter
+    fun fromTraceLocationType(type: TraceLocation.Type) = type.value
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c681d3a92acf6c123ba70af40d17a86447ae178b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntity.kt
@@ -0,0 +1,35 @@
+package de.rki.coronawarnapp.eventregistration.storage.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import org.joda.time.Instant
+
+@Entity(tableName = "traceLocations")
+data class TraceLocationEntity(
+
+    @PrimaryKey @ColumnInfo(name = "guid") val guid: String,
+    @ColumnInfo(name = "version") val version: Int,
+    @ColumnInfo(name = "type") val type: TraceLocation.Type,
+    @ColumnInfo(name = "description") val description: String,
+    @ColumnInfo(name = "address") val address: String,
+    @ColumnInfo(name = "startDate") val startDate: Instant?,
+    @ColumnInfo(name = "endDate") val endDate: Instant?,
+    @ColumnInfo(name = "defaultCheckInLengthInMinutes") val defaultCheckInLengthInMinutes: Int?,
+    @ColumnInfo(name = "signature") val signature: String
+
+)
+
+fun TraceLocation.toTraceLocationEntity(): TraceLocationEntity =
+    TraceLocationEntity(
+        guid = guid,
+        type = type,
+        description = description,
+        address = address,
+        startDate = startDate,
+        endDate = endDate,
+        defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes,
+        signature = signature,
+        version = version
+    )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ab931ee3c3f22c898054b1e4391b34213c6a6eae
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt
@@ -0,0 +1,56 @@
+package de.rki.coronawarnapp.eventregistration.storage.repo
+
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import de.rki.coronawarnapp.eventregistration.events.toTraceLocations
+import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase
+import de.rki.coronawarnapp.eventregistration.storage.dao.TraceLocationDao
+import de.rki.coronawarnapp.eventregistration.storage.entity.toTraceLocationEntity
+import de.rki.coronawarnapp.util.coroutine.AppScope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class DefaultTraceLocationRepository @Inject constructor(
+    traceLocationDatabaseFactory: TraceLocationDatabase.Factory,
+    @AppScope private val appScope: CoroutineScope
+) : TraceLocationRepository {
+
+    private val traceLocationDatabase: TraceLocationDatabase by lazy {
+        traceLocationDatabaseFactory.create()
+    }
+
+    private val traceLocationDao: TraceLocationDao by lazy {
+        traceLocationDatabase.traceLocationDao()
+    }
+
+    override val allTraceLocations: Flow<List<TraceLocation>>
+        get() = traceLocationDao.allEntries().map { it.toTraceLocations() }
+
+    override fun addTraceLocation(event: TraceLocation) {
+        appScope.launch {
+            Timber.d("Add hosted event: $event")
+            val eventEntity = event.toTraceLocationEntity()
+            traceLocationDao.insert(eventEntity)
+        }
+    }
+
+    override fun deleteTraceLocation(event: TraceLocation) {
+        appScope.launch {
+            Timber.d("Delete hosted event: $event")
+            val eventEntity = event.toTraceLocationEntity()
+            traceLocationDao.delete(eventEntity)
+        }
+    }
+
+    override fun deleteAllTraceLocations() {
+        appScope.launch {
+            Timber.d("Delete all hosted events.")
+            traceLocationDao.deleteAll()
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..88576a9cfc9eca4563f129d2f2820c7ab1e9ebee
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt
@@ -0,0 +1,15 @@
+package de.rki.coronawarnapp.eventregistration.storage.repo
+
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import kotlinx.coroutines.flow.Flow
+
+interface TraceLocationRepository {
+
+    val allTraceLocations: Flow<List<TraceLocation>>
+
+    fun addTraceLocation(event: TraceLocation)
+
+    fun deleteTraceLocation(event: TraceLocation)
+
+    fun deleteAllTraceLocations()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt
index 3863ee8015d64f51679e2a9f09645580fad0d187..4a8dea0d8b21ac5ecbcae1fd927a7b38c61d48a1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt
@@ -54,6 +54,7 @@ fun BottomNavigationView.setupWithNavController2(
                 // For destinations that always show the bottom bar
                 val inShowList = destination.id in listOf(
                     R.id.mainFragment,
+                    R.id.checkInsFragment,
                     R.id.contactDiaryOverviewFragment
                 )
                 // For destinations that can show or hide the bottom bar in different cases
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a87ab34f421700514e26c8d5ce57a7357e9f973a
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt
@@ -0,0 +1,23 @@
+package de.rki.coronawarnapp.ui.eventregistration
+
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment
+import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsModule
+import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInFragment
+import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInModule
+import de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeFragment
+import de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeModule
+
+@Module
+internal abstract class EventRegistrationUIModule {
+
+    @ContributesAndroidInjector(modules = [ScanCheckInQrCodeModule::class])
+    abstract fun scanCheckInQrCodeFragment(): ScanCheckInQrCodeFragment
+
+    @ContributesAndroidInjector(modules = [ConfirmCheckInModule::class])
+    abstract fun confirmCheckInFragment(): ConfirmCheckInFragment
+
+    @ContributesAndroidInjector(modules = [CheckInsModule::class])
+    abstract fun checkInsFragment(): CheckInsFragment
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2ab0eb9fa9a49a81953fbe7624ecbfdad17917db
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsFragment.kt
@@ -0,0 +1,65 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.View
+import androidx.core.net.toUri
+import androidx.navigation.fragment.FragmentNavigatorExtras
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.google.android.material.transition.Hold
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentCheckInsBinding
+import de.rki.coronawarnapp.util.di.AutoInject
+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
+
+class CheckInsFragment : Fragment(R.layout.fragment_check_ins), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val viewModel: CheckInsViewModel by cwaViewModels { viewModelFactory }
+    private val binding: FragmentCheckInsBinding by viewBindingLazy()
+
+    // Encoded uri is a one-time use data and then cleared
+    private val uri: String?
+        get() = navArgs<CheckInsFragmentArgs>().value
+            .uri
+            .also { arguments?.clear() }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        exitTransition = Hold()
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        with(binding.scanCheckinQrcodeFab) {
+            setOnClickListener {
+                findNavController().navigate(
+                    R.id.action_checkInsFragment_to_scanCheckInQrCodeFragment,
+                    null,
+                    null,
+                    FragmentNavigatorExtras(this to transitionName)
+                )
+            }
+        }
+
+        uri?.let { viewModel.verifyUri(it) }
+        viewModel.verifyResult.observe2(this) {
+            doNavigate(
+                CheckInsFragmentDirections
+                    .actionCheckInsFragmentToConfirmCheckInFragment(it.toVerifiedTraceLocation())
+            )
+        }
+    }
+
+    companion object {
+        fun uri(rootUri: String): Uri = "coronawarnapp://check-ins/$rootUri".toUri()
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d4b68e13ea370f9e4ddfbee26a9deab8b45b392f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin
+
+import dagger.Binds
+import dagger.Module
+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 CheckInsModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(CheckInsViewModel::class)
+    abstract fun checkInsFragment(
+        factory: CheckInsViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c72f034e320f2f289cdbbbb1f9e4889145e91d85
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/CheckInsViewModel.kt
@@ -0,0 +1,43 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier
+import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult
+import de.rki.coronawarnapp.eventregistration.checkins.qrcode.isValidQRCodeUri
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.reporting.report
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import timber.log.Timber
+
+class CheckInsViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider,
+    private val qrCodeVerifier: QRCodeVerifier
+) : CWAViewModel(dispatcherProvider) {
+
+    private val verifyResultData = MutableLiveData<QRCodeVerifyResult>()
+    val verifyResult: LiveData<QRCodeVerifyResult> = verifyResultData
+
+    fun verifyUri(uri: String) = launch {
+        try {
+            Timber.i("uri: $uri")
+            if (!uri.isValidQRCodeUri())
+                throw IllegalArgumentException("Invalid uri: $uri")
+
+            val encodedEvent = uri.substringAfterLast("/")
+            val verifyResult = qrCodeVerifier.verify(encodedEvent)
+            Timber.i("verifyResult: $verifyResult")
+            verifyResultData.postValue(verifyResult)
+        } catch (e: Exception) {
+            Timber.d(e, "TraceLocation verification failed")
+            e.report(ExceptionCategory.INTERNAL)
+        }
+    }
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<CheckInsViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocation.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b8c61317dba996501964090f76d8caa5774aeb2d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkin/VerifiedTraceLocation.kt
@@ -0,0 +1,35 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin
+
+import android.os.Parcelable
+import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult
+import kotlinx.parcelize.Parcelize
+import okio.ByteString.Companion.toByteString
+import org.joda.time.Instant
+import java.util.concurrent.TimeUnit
+
+@Parcelize
+data class VerifiedTraceLocation(
+    val guid: String,
+    val description: String?,
+    val start: Instant?,
+    val end: Instant?,
+    val defaultCheckInLengthInMinutes: Int,
+    // TODO add required properties to confirm check-in
+) : Parcelable
+
+fun QRCodeVerifyResult.toVerifiedTraceLocation() =
+    with(singedTraceLocation.location) {
+        VerifiedTraceLocation(
+            guid = guid.toByteArray().toByteString().base64(),
+            start = startTimestamp.toInstant(),
+            end = endTimestamp.toInstant(),
+            description = description,
+            defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes
+        )
+    }
+
+/**
+ * Converts time in seconds into [Instant]
+ */
+private fun Long.toInstant() =
+    if (this == 0L) null else Instant.ofEpochMilli(TimeUnit.SECONDS.toMillis(this))
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..804b57eb9cb2ae9b46e8412356bced243ea9f228
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInFragment.kt
@@ -0,0 +1,48 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.confirm
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.navArgs
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentConfirmCheckInBinding
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.popBackStack
+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
+
+class ConfirmCheckInFragment : Fragment(R.layout.fragment_confirm_check_in), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+
+    private val viewModel: ConfirmCheckInViewModel by cwaViewModels { viewModelFactory }
+    private val binding: FragmentConfirmCheckInBinding by viewBindingLazy()
+    private val args by navArgs<ConfirmCheckInFragmentArgs>()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        with(binding) {
+            toolbar.setNavigationOnClickListener { viewModel.onClose() }
+            confirmButton.setOnClickListener { viewModel.onConfirmTraceLocation() }
+            // TODO bind final UI
+            eventGuid.text = "GUID: %s".format(args.traceLocation.guid)
+            startTime.text = "Start time: %s".format(args.traceLocation.start)
+            endTime.text = "End time: %s".format(args.traceLocation.end)
+            description.text = "Description: %s".format(args.traceLocation.description)
+        }
+
+        viewModel.events.observe2(this) { navEvent ->
+            when (navEvent) {
+                ConfirmCheckInNavigation.BackNavigation -> popBackStack()
+                ConfirmCheckInNavigation.ConfirmNavigation -> {
+                    // TODO Navigate to the rightful destination
+                    popBackStack()
+                }
+            }
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fa18d2e2e0caabeeac574cfb141687db6f4dfe75
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.confirm
+
+import dagger.Binds
+import dagger.Module
+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 ConfirmCheckInModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(ConfirmCheckInViewModel::class)
+    abstract fun confirmCheckInFragment(
+        factory: ConfirmCheckInViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInNavigation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInNavigation.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0620735ed73a68cd34c6842eb3f909de36091ac0
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInNavigation.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.confirm
+
+sealed class ConfirmCheckInNavigation {
+    object BackNavigation : ConfirmCheckInNavigation()
+    object ConfirmNavigation : ConfirmCheckInNavigation()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b3931016005d6dbe248ab12e52d20af88c4e66bb
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInViewModel.kt
@@ -0,0 +1,22 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.confirm
+
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class ConfirmCheckInViewModel @AssistedInject constructor() : CWAViewModel() {
+    val events = SingleLiveEvent<ConfirmCheckInNavigation>()
+
+    fun onClose() {
+        events.value = ConfirmCheckInNavigation.BackNavigation
+    }
+
+    fun onConfirmTraceLocation() {
+        events.value = ConfirmCheckInNavigation.ConfirmNavigation
+    }
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<ConfirmCheckInViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c8d757c662b984b9c7eaf5636b934071740f2111
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeFragment.kt
@@ -0,0 +1,162 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.scan
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT
+import androidx.fragment.app.Fragment
+import androidx.navigation.NavOptions
+import androidx.navigation.fragment.findNavController
+import com.google.android.material.transition.MaterialContainerTransform
+import com.google.zxing.BarcodeFormat
+import com.journeyapps.barcodescanner.DefaultDecoderFactory
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentScanCheckInQrCodeBinding
+import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment
+import de.rki.coronawarnapp.util.CameraPermissionHelper
+import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.popBackStack
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
+import timber.log.Timber
+import javax.inject.Inject
+
+class ScanCheckInQrCodeFragment :
+    Fragment(R.layout.fragment_scan_check_in_qr_code),
+    AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val viewModel: ScanCheckInQrCodeViewModel by cwaViewModels { viewModelFactory }
+
+    private val binding: FragmentScanCheckInQrCodeBinding by viewBindingLazy()
+    private var showsPermissionDialog = false
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        sharedElementEnterTransition = MaterialContainerTransform()
+        sharedElementReturnTransition = MaterialContainerTransform()
+    }
+
+    override fun onViewCreated(
+        view: View,
+        savedInstanceState: Bundle?
+    ) {
+        with(binding) {
+            checkInQrCodeScanTorch.setOnCheckedChangeListener { _, isChecked ->
+                binding.checkInQrCodeScanPreview.setTorch(isChecked)
+            }
+            checkInQrCodeScanClose.setOnClickListener { viewModel.onNavigateUp() }
+            checkInQrCodeScanPreview.decoderFactory = DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE))
+            checkInQrCodeScanViewfinderView.setCameraPreview(binding.checkInQrCodeScanPreview)
+        }
+
+        viewModel.events.observe2(this) { navEvent ->
+            when (navEvent) {
+                is ScanCheckInQrCodeNavigation.BackNavigation -> popBackStack()
+                is ScanCheckInQrCodeNavigation.ScanResultNavigation -> {
+                    Timber.i(navEvent.uri)
+                    findNavController().navigate(
+                        CheckInsFragment.uri(navEvent.uri),
+                        NavOptions.Builder()
+                            .setPopUpTo(R.id.checkInsFragment, true)
+                            .build()
+                    )
+                }
+            }
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        binding.checkInQrCodeScanContainer.sendAccessibilityEvent(TYPE_ANNOUNCEMENT)
+        if (CameraPermissionHelper.hasCameraPermission(requireActivity())) {
+            binding.checkInQrCodeScanPreview.resume()
+            startDecode()
+            return
+        }
+        if (showsPermissionDialog) return
+
+        requestCameraPermission()
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        if (requestCode == REQUEST_CAMERA_PERMISSION_CODE &&
+            grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_DENIED
+        ) {
+            if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
+                showCameraPermissionRationaleDialog()
+            } else {
+                // User permanently denied access to the camera
+                showCameraPermissionDeniedDialog()
+            }
+        }
+    }
+
+    private fun startDecode() = binding.checkInQrCodeScanPreview
+        .decodeSingle { barcodeResult ->
+            viewModel.onScanResult(barcodeResult)
+        }
+
+    private fun showCameraPermissionDeniedDialog() {
+        val permissionDeniedDialog = DialogHelper.DialogInstance(
+            requireActivity(),
+            // TODO use strings for this screen
+            R.string.submission_qr_code_scan_permission_denied_dialog_headline,
+            R.string.submission_qr_code_scan_permission_denied_dialog_body,
+            R.string.submission_qr_code_scan_permission_denied_dialog_button,
+            cancelable = false,
+            positiveButtonFunction = {
+                showsPermissionDialog = false
+                viewModel.onNavigateUp()
+            }
+        )
+        showsPermissionDialog = true
+        DialogHelper.showDialog(permissionDeniedDialog)
+    }
+
+    private fun showCameraPermissionRationaleDialog() {
+        val cameraPermissionRationaleDialogInstance = DialogHelper.DialogInstance(
+            requireActivity(),
+            // TODO use strings for this screen
+            R.string.submission_qr_code_scan_permission_rationale_dialog_headline,
+            R.string.submission_qr_code_scan_permission_rationale_dialog_body,
+            R.string.submission_qr_code_scan_permission_rationale_dialog_button_positive,
+            R.string.submission_qr_code_scan_permission_rationale_dialog_button_negative,
+            false,
+            {
+                showsPermissionDialog = false
+                requestCameraPermission()
+            },
+            {
+                showsPermissionDialog = false
+                viewModel.onNavigateUp()
+            }
+        )
+
+        showsPermissionDialog = true
+        DialogHelper.showDialog(cameraPermissionRationaleDialogInstance)
+    }
+
+    private fun requestCameraPermission() = requestPermissions(
+        arrayOf(Manifest.permission.CAMERA),
+        REQUEST_CAMERA_PERMISSION_CODE
+    )
+
+    override fun onPause() {
+        super.onPause()
+        binding.checkInQrCodeScanPreview.pause()
+    }
+
+    companion object {
+        private const val REQUEST_CAMERA_PERMISSION_CODE = 4000
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..37349adb9a52a9b8f936f64993e9395d8f43da8a
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.scan
+
+import dagger.Binds
+import dagger.Module
+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 ScanCheckInQrCodeModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(ScanCheckInQrCodeViewModel::class)
+    abstract fun scanCheckInQrCodeFragment(
+        factory: ScanCheckInQrCodeViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeNavigation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeNavigation.kt
new file mode 100644
index 0000000000000000000000000000000000000000..db3c562d8276579ab79d50043d0b347b67895760
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeNavigation.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.scan
+
+sealed class ScanCheckInQrCodeNavigation {
+    object BackNavigation : ScanCheckInQrCodeNavigation()
+    data class ScanResultNavigation(val uri: String) : ScanCheckInQrCodeNavigation()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5452829bd49a50f995ae826f1041798b12e52d84
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeViewModel.kt
@@ -0,0 +1,25 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.scan
+
+import com.journeyapps.barcodescanner.BarcodeResult
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class ScanCheckInQrCodeViewModel @AssistedInject constructor() : CWAViewModel() {
+    val events = SingleLiveEvent<ScanCheckInQrCodeNavigation>()
+
+    fun onNavigateUp() {
+        events.value = ScanCheckInQrCodeNavigation.BackNavigation
+    }
+
+    fun onScanResult(barcodeResult: BarcodeResult) {
+        events.value = ScanCheckInQrCodeNavigation.ScanResultNavigation(
+            barcodeResult.result.text
+        )
+    }
+
+    @AssistedFactory
+    interface Factory : SimpleCWAViewModelFactory<ScanCheckInQrCodeViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt
index 90c3fe4bce7c607f8ef654177a82015d632e8a30..27a61dabff03e3af8e5693db96de5f57c5c005fd 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt
@@ -9,7 +9,6 @@ import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity
 import de.rki.coronawarnapp.util.di.AppInjector
-import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
 import javax.inject.Inject
@@ -30,12 +29,12 @@ class LauncherActivity : AppCompatActivity() {
         vm.events.observe(this) {
             when (it) {
                 LauncherEvent.GoToOnboarding -> {
-                    OnboardingActivity.start(this, AppShortcutsHelper.getShortcutType(intent))
+                    OnboardingActivity.start(this, intent)
                     this.overridePendingTransition(0, 0)
                     finish()
                 }
                 LauncherEvent.GoToMainActivity -> {
-                    MainActivity.start(this, AppShortcutsHelper.getShortcutType(intent))
+                    MainActivity.start(this, intent)
                     this.overridePendingTransition(0, 0)
                     finish()
                 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
index 545557ddd88760054a96560d618d8db498bc01f7..12ba28358a75be28d1cfa0d45252d25ffa996e32 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt
@@ -24,6 +24,7 @@ import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsS
 import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.ui.base.startActivitySafely
+import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment
 import de.rki.coronawarnapp.ui.setupWithNavController2
 import de.rki.coronawarnapp.util.AppShortcuts
 import de.rki.coronawarnapp.util.CWADebug
@@ -31,11 +32,13 @@ import de.rki.coronawarnapp.util.ConnectivityHelper
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.device.PowerManagement
 import de.rki.coronawarnapp.util.di.AppInjector
+import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper.Companion.getShortcutExtra
 import de.rki.coronawarnapp.util.ui.findNavController
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
 import org.joda.time.LocalDate
+import timber.log.Timber
 import javax.inject.Inject
 
 /**
@@ -47,24 +50,14 @@ import javax.inject.Inject
  */
 class MainActivity : AppCompatActivity(), HasAndroidInjector {
     companion object {
-        private const val EXTRA_DATA = "shortcut"
-
-        fun start(context: Context, shortcut: AppShortcuts? = null) {
-            val intent = Intent(context, MainActivity::class.java).apply {
-                if (shortcut != null) {
-                    putExtra(EXTRA_DATA, shortcut.toString())
-                    flags = flags or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
-                }
+        fun start(context: Context, launchIntent: Intent) {
+            Intent(context, MainActivity::class.java).apply {
+                flags = flags or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
+                Timber.i("launchIntent:$launchIntent")
+                fillIn(launchIntent, Intent.FILL_IN_DATA)
+                Timber.i("filledIntent:$this")
+                context.startActivity(this)
             }
-            context.startActivity(intent)
-        }
-
-        private fun getShortcutFromIntent(intent: Intent): AppShortcuts? {
-            val extra = intent.getStringExtra(EXTRA_DATA)
-            if (extra != null) {
-                return AppShortcuts.valueOf(extra)
-            }
-            return null
         }
     }
 
@@ -80,6 +73,8 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector {
     private val FragmentManager.currentNavigationFragment: Fragment?
         get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first()
 
+    private val navController by lazy { supportFragmentManager.findNavController(R.id.nav_host_fragment) }
+
     @Inject lateinit var powerManagement: PowerManagement
     @Inject lateinit var deadmanScheduler: DeadmanNotificationScheduler
     @Inject lateinit var contactDiaryWorkScheduler: ContactDiaryWorkScheduler
@@ -106,7 +101,6 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector {
             showEnergyOptimizedEnabledForBackground()
         }
 
-        val navController = supportFragmentManager.findNavController(R.id.nav_host_fragment)
         binding.mainBottomNavigation.setupWithNavController2(navController) {
             vm.onBottomNavSelected()
         }
@@ -119,16 +113,21 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector {
         }
     }
 
+    override fun onNewIntent(intent: Intent?) {
+        super.onNewIntent(intent)
+        Timber.i("onNewIntent:$intent")
+        navigateByIntentUri(intent)
+    }
+
     private fun processExtraParameters() {
-        when (getShortcutFromIntent(intent)) {
-            AppShortcuts.CONTACT_DIARY -> {
-                goToContactJournal()
-            }
+        when (intent.getShortcutExtra()) {
+            AppShortcuts.CONTACT_DIARY -> goToContactJournal()
         }
+
+        navigateByIntentUri(intent)
     }
 
     private fun goToContactJournal() {
-        val navController = supportFragmentManager.findNavController(R.id.nav_host_fragment)
         findViewById<BottomNavigationView>(R.id.main_bottom_navigation).selectedItemId =
             R.id.contact_diary_nav_graph
         val nestedGraph = navController.graph.findNode(R.id.contact_diary_nav_graph) as NavGraph
@@ -155,6 +154,12 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector {
         }
     }
 
+    private fun navigateByIntentUri(intent: Intent?) {
+        val uri = intent?.data ?: return
+        Timber.i("Uri:$uri")
+        navController.navigate(CheckInsFragment.uri(uri.toString()))
+    }
+
     /**
      * Register callbacks.
      */
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 72222d127801669f8442f1c624e67c85de13ce85..dc98543bbc48644fdbebc937c055cb39c33d74aa 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
@@ -8,6 +8,7 @@ import de.rki.coronawarnapp.datadonation.analytics.ui.AnalyticsUIModule
 import de.rki.coronawarnapp.release.NewReleaseInfoFragment
 import de.rki.coronawarnapp.release.NewReleaseInfoFragmentModule
 import de.rki.coronawarnapp.tracing.ui.details.TracingDetailsFragmentModule
+import de.rki.coronawarnapp.ui.eventregistration.EventRegistrationUIModule
 import de.rki.coronawarnapp.ui.information.InformationFragmentModule
 import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragment
 import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragmentModule
@@ -32,7 +33,8 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
         SubmissionFragmentModule::class,
         InformationFragmentModule::class,
         NewReleaseInfoFragmentModule::class,
-        AnalyticsUIModule::class
+        AnalyticsUIModule::class,
+        EventRegistrationUIModule::class,
     ]
 )
 abstract class MainActivityModule {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt
index 3888efc6398c11c741d3fc29bc6d796e1c36254e..1309c84def1530eaccad02f74ac8987bede1055f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt
@@ -15,9 +15,9 @@ import de.rki.coronawarnapp.environment.BuildConfigWrap
 import de.rki.coronawarnapp.main.CWASettings
 import de.rki.coronawarnapp.storage.OnboardingSettings
 import de.rki.coronawarnapp.ui.main.MainActivity
-import de.rki.coronawarnapp.util.AppShortcuts
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.di.AppInjector
+import timber.log.Timber
 import javax.inject.Inject
 
 /**
@@ -26,22 +26,16 @@ import javax.inject.Inject
  */
 class OnboardingActivity : AppCompatActivity(), LifecycleObserver, HasAndroidInjector {
     companion object {
-        private val TAG: String? = OnboardingActivity::class.simpleName
-        private const val EXTRA_DATA = "shortcut"
 
-        fun start(context: Context, shortcut: AppShortcuts? = null) {
-            val intent = Intent(context, OnboardingActivity::class.java).apply {
-                putExtra(EXTRA_DATA, shortcut?.toString())
+        fun start(context: Context, launchIntent: Intent? = null) {
+            val intent = Intent(context, OnboardingActivity::class.java)
+            Timber.i("launchIntent:$launchIntent")
+            launchIntent?.let {
+                intent.fillIn(it, Intent.FILL_IN_DATA)
+                Timber.i("filledIntent:$intent")
             }
             context.startActivity(intent)
         }
-
-        fun getShortcutFromIntent(intent: Intent?): AppShortcuts? {
-            intent?.getStringExtra(EXTRA_DATA)?.let {
-                return AppShortcuts.valueOf(it)
-            }
-            return null
-        }
     }
 
     @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
@@ -74,12 +68,9 @@ class OnboardingActivity : AppCompatActivity(), LifecycleObserver, HasAndroidInj
 
     fun completeOnboarding() {
         onboardingSettings.onboardingCompletedTimestamp = timeStamper.nowUTC
-
-        settings.lastChangelogVersion.update {
-            BuildConfigWrap.VERSION_CODE
-        }
-
-        MainActivity.start(this)
+        settings.lastChangelogVersion.update { BuildConfigWrap.VERSION_CODE }
+        settings.lastChangelogVersion.update { BuildConfigWrap.VERSION_CODE }
+        MainActivity.start(this, intent)
         finish()
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingFragment.kt
index 7d7c6475ff8122c86e7ae6b8e42260c447f8da71..e4d0d9a7ae1b209bb75af52955d50d84d02541ae 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingFragment.kt
@@ -41,7 +41,7 @@ class OnboardingLoadingFragment : Fragment(R.layout.onboaring_loading_layout), A
                             .actionLoadingFragmentToOnboardingFragment()
                     )
                 OnboardingFragmentEvents.OnboardingDone -> {
-                    MainActivity.start(requireContext(), OnboardingActivity.getShortcutFromIntent(activity?.intent))
+                    MainActivity.start(requireContext(), requireActivity().intent)
                     activity?.overridePendingTransition(0, 0)
                     activity?.finish()
                 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
index 08eec626f20d6005b8aec183c6fa79f9a2fc0a7c..37b1347df6e68a0c8022dea5dfb5b76c24dc8280 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
@@ -11,6 +11,7 @@ import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
 import de.rki.coronawarnapp.datadonation.survey.SurveySettings
 import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysSettings
 import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository
+import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
 import de.rki.coronawarnapp.main.CWASettings
 import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
@@ -49,7 +50,8 @@ class DataReset @Inject constructor(
     private val bugReportingSettings: BugReportingSettings,
     private val tracingSettings: TracingSettings,
     private val onboardingSettings: OnboardingSettings,
-    private val submissionSettings: SubmissionSettings
+    private val submissionSettings: SubmissionSettings,
+    private val traceLocationRepository: TraceLocationRepository,
 ) {
 
     private val mutex = Mutex()
@@ -86,6 +88,8 @@ class DataReset @Inject constructor(
 
         bugReportingSettings.clear()
 
+        traceLocationRepository.deleteAllTraceLocations()
+
         Timber.w("CWA LOCAL DATA DELETION COMPLETED.")
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
index 35ee578c350d1feeaaaf43f1a4a3e9852c896171..a2c6ec3d39243cc97bae7269e18dbe1bb09fbde2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt
@@ -16,6 +16,7 @@ import de.rki.coronawarnapp.diagnosiskeys.DiagnosisKeysModule
 import de.rki.coronawarnapp.diagnosiskeys.DownloadDiagnosisKeysTaskModule
 import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository
 import de.rki.coronawarnapp.environment.EnvironmentModule
+import de.rki.coronawarnapp.eventregistration.EventRegistrationModule
 import de.rki.coronawarnapp.http.HttpModule
 import de.rki.coronawarnapp.nearby.ENFClient
 import de.rki.coronawarnapp.nearby.ENFModule
@@ -73,7 +74,8 @@ import javax.inject.Singleton
         WorkerBinder::class,
         StatisticsModule::class,
         DataDonationModule::class,
-        SecurityModule::class
+        SecurityModule::class,
+        EventRegistrationModule::class,
     ]
 )
 interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileExtensions.kt
index 8bf768b7824255e8be19e89883b8ae0b2172823d..f5b9f83495c522357b9f1c5b65e200c423cabdcd 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileExtensions.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileExtensions.kt
@@ -4,5 +4,6 @@ import java.io.File
 
 fun File.determineMimeType(): String = when {
     name.endsWith(".zip") -> "application/zip"
+    name.endsWith(".pdf") -> "application/pdf"
     else -> throw UnsupportedOperationException("Unsupported MIME type: $path")
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt
index 4303506d6e561362417e660cb3a0e2833f31af53..488c8a12f3a23a44cb9f8a7ad51cfb762d7f346b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt
@@ -50,6 +50,38 @@ class FileSharing @Inject constructor(
         }
     }
 
+    fun getFileIntentProvider(
+        path: File,
+        title: String,
+        @StringRes chooserTitle: Int? = null
+    ): FileIntentProvider = object : FileIntentProvider {
+        override fun intent(activity: Activity): Intent {
+            val builder = ShareCompat.IntentBuilder.from(activity).apply {
+                setType(path.determineMimeType())
+                setStream(getFileUri(path))
+                setSubject(title)
+                chooserTitle?.let { setChooserTitle(it) }
+            }
+
+            val intent = if (chooserTitle != null) {
+                builder.createChooserIntent()
+            } else {
+                builder.intent
+            }
+            return intent.apply {
+                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                Timber.tag(TAG).d("Intent created %s", this)
+            }
+        }
+
+        override val file: File = path
+    }
+
+    interface FileIntentProvider {
+        fun intent(activity: Activity): Intent
+        val file: File
+    }
+
     interface ShareIntentProvider {
         fun get(activity: Activity): Intent
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/shortcuts/AppShortcutsHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/shortcuts/AppShortcutsHelper.kt
index 0aeb1345d353a2b058806d25494139d272b83d14..a12907e37134b29d20e21c7077ace62c2b588ed4 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/shortcuts/AppShortcutsHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/shortcuts/AppShortcutsHelper.kt
@@ -36,18 +36,17 @@ class AppShortcutsHelper @Inject constructor(@AppContext private val context: Co
 
     private fun createContactDiaryIntent() = Intent(context, LauncherActivity::class.java).apply {
         action = Intent.ACTION_VIEW
-        putExtra(SHORTCUT_EXTRA_ID, AppShortcuts.CONTACT_DIARY.toString())
+        putExtra(SHORTCUT_EXTRA, AppShortcuts.CONTACT_DIARY.toString())
     }
 
     companion object {
         private const val CONTACT_DIARY_SHORTCUT_ID = "contact_diary_id"
-        private const val SHORTCUT_EXTRA_ID = "shortcut_extra"
+        const val SHORTCUT_EXTRA = "shortcut_extra"
 
-        fun getShortcutType(intent: Intent): AppShortcuts? {
-            intent.getStringExtra(SHORTCUT_EXTRA_ID)?.let {
+        fun Intent.getShortcutExtra(): AppShortcuts? {
+            getStringExtra(SHORTCUT_EXTRA)?.let {
                 return AppShortcuts.valueOf(it)
             }
-
             return null
         }
     }
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_nav_check_in.xml b/Corona-Warn-App/src/main/res/drawable/ic_nav_check_in.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a0597989a33e9a8cfb17dc58612d6016840fc97e
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_nav_check_in.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="23dp"
+    android:height="22dp"
+    android:viewportWidth="23"
+    android:viewportHeight="22">
+  <path
+      android:pathData="M21.1465,7.581C21.7158,7.581 22.0166,7.2588 22.0166,6.7002V4.0684C22.0166,1.8555 20.8887,0.749 18.6436,0.749H16.0117C15.4531,0.749 15.1416,1.0498 15.1416,1.6084C15.1416,2.167 15.4531,2.4785 16.0117,2.4785H18.6113C19.6748,2.4785 20.2871,3.0478 20.2871,4.165V6.7002C20.2871,7.2588 20.5986,7.581 21.1465,7.581ZM1.8428,7.581C2.4121,7.581 2.7129,7.2588 2.7129,6.7002V4.165C2.7129,3.0478 3.3037,2.4785 4.3779,2.4785H6.9775C7.5469,2.4785 7.8584,2.167 7.8584,1.6084C7.8584,1.0498 7.5469,0.749 6.9775,0.749H4.3565C2.1113,0.749 0.9834,1.8555 0.9834,4.0684V6.7002C0.9834,7.2588 1.2949,7.581 1.8428,7.581ZM7.3106,17.0771H15.668C16.4199,17.0771 16.7637,16.7871 16.7637,16.0352V6.4746C16.7637,5.7227 16.4199,5.4326 15.668,5.4326H7.3106C6.5586,5.4326 6.2148,5.7227 6.2148,6.4746V16.0352C6.2148,16.7871 6.5586,17.0771 7.3106,17.0771ZM9.169,9.3857C8.8145,9.3857 8.5889,9.1494 8.5889,8.8164C8.5889,8.4619 8.8145,8.2256 9.169,8.2256H13.8203C14.1748,8.2256 14.4111,8.4619 14.4111,8.8164C14.4111,9.1494 14.1748,9.3857 13.8203,9.3857H9.169ZM9.169,11.6953C8.8145,11.6953 8.5889,11.4482 8.5889,11.1152C8.5889,10.7715 8.8145,10.5244 9.169,10.5244H11.7363C12.0801,10.5244 12.3164,10.7715 12.3164,11.1152C12.3164,11.4482 12.0801,11.6953 11.7363,11.6953H9.169ZM4.3565,21.7715H6.9775C7.5469,21.7715 7.8584,21.46 7.8584,20.9121C7.8584,20.3535 7.5469,20.042 6.9775,20.042H4.3779C3.3037,20.042 2.7129,19.4727 2.7129,18.3555V15.8203C2.7129,15.251 2.4014,14.9395 1.8428,14.9395C1.2842,14.9395 0.9834,15.251 0.9834,15.8203V18.4414C0.9834,20.665 2.1113,21.7715 4.3565,21.7715ZM16.0117,21.7715H18.6436C20.8887,21.7715 22.0166,20.6543 22.0166,18.4414V15.8203C22.0166,15.251 21.7051,14.9395 21.1465,14.9395C20.5879,14.9395 20.2871,15.251 20.2871,15.8203V18.3555C20.2871,19.4727 19.6748,20.042 18.6113,20.042H16.0117C15.4531,20.042 15.1416,20.3535 15.1416,20.9121C15.1416,21.46 15.4531,21.7715 16.0117,21.7715Z"
+      android:fillColor="#949494"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_check_ins.xml b/Corona-Warn-App/src/main/res/layout/fragment_check_ins.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1053da359f440f76e1861823d2946dea460a8370
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/fragment_check_ins.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/content_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.google.android.material.appbar.MaterialToolbar
+        android:id="@+id/toolbar"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/check_ins_recycler"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/toolbar" />
+
+    <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+        android:id="@+id/scan_checkin_qrcode_fab"
+        style="@style/Widget.App.ExtendedFloatingActionButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/spacing_normal"
+        android:text="@string/scan_check_in_qr_code"
+        android:transitionName="shared_element_container"
+        app:icon="@drawable/ic_nav_check_in"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_confirm_check_in.xml b/Corona-Warn-App/src/main/res/layout/fragment_confirm_check_in.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b5b111f19b1de861eafe5a4e015ef7416d026c45
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/fragment_confirm_check_in.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ui.eventregistration.attendee.confirm.ConfirmCheckInFragment">
+
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/toolbar"
+        style="@style/CWAToolbar.Close"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!-- TODO implement actual UI -->
+    <LinearLayout
+        style="@style/Card"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dp"
+        android:orientation="vertical"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/toolbar">
+        <TextView
+            android:id="@+id/eventGuid"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:id="@+id/startTime"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <TextView
+            android:id="@+id/endTime"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <TextView
+            android:id="@+id/description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/confirmButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Confirm" />
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_scan_check_in_qr_code.xml b/Corona-Warn-App/src/main/res/layout/fragment_scan_check_in_qr_code.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b14d08f71b9816b0b02be971c69885988077d54b
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/fragment_scan_check_in_qr_code.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/check_in_qr_code_scan_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:contentDescription="@string/submission_qr_code_scan_title"
+    android:transitionName="shared_element_container">
+
+    <com.journeyapps.barcodescanner.BarcodeView
+        android:id="@+id/check_in_qr_code_scan_preview"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:zxing_framing_rect_height="@dimen/submission_scan_qr_code_viewfinder_size"
+        app:zxing_framing_rect_width="@dimen/submission_scan_qr_code_viewfinder_size">
+
+    </com.journeyapps.barcodescanner.BarcodeView>
+
+    <com.journeyapps.barcodescanner.ViewfinderView
+        android:id="@+id/check_in_qr_code_scan_viewfinder_view"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:zxing_viewfinder_laser_visibility="false" />
+
+    <TextView
+        android:id="@+id/check_in_qr_code_scan_body"
+        style="@style/registrationQRCodeScanBody"
+        android:layout_width="@dimen/submission_scan_qr_code_viewfinder_size"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/submission_scan_qr_code_viewfinder_center_offset"
+        android:text="@string/submission_qr_code_scan_body"
+        app:layout_constraintEnd_toEndOf="@id/check_in_qr_code_scan_preview"
+        app:layout_constraintStart_toStartOf="@id/check_in_qr_code_scan_preview"
+        app:layout_constraintTop_toBottomOf="@id/check_in_qr_code_scan_guideline_center" />
+
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/check_in_qr_code_scan_close"
+        style="@style/buttonIcon"
+        android:layout_width="@dimen/icon_size_button"
+        android:layout_height="@dimen/icon_size_button"
+        app:layout_constraintBottom_toTopOf="@id/check_in_qr_code_scan_guideline_top"
+        app:layout_constraintEnd_toStartOf="@id/guideline_start"
+        app:layout_constraintStart_toStartOf="@id/guideline_start"
+        app:layout_constraintTop_toTopOf="@id/check_in_qr_code_scan_guideline_top">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            style="@style/iconStable"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@string/accessibility_close"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/ic_close" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <ToggleButton
+        android:id="@+id/check_in_qr_code_scan_torch"
+        android:layout_width="@dimen/icon_size_button"
+        android:layout_height="@dimen/icon_size_button"
+        android:background="@drawable/ic_registration_qr_code_scan_torch_toggle"
+        android:backgroundTint="@color/colorStableLight"
+        android:textOff=""
+        android:textOn=""
+        app:layout_constraintBottom_toTopOf="@id/check_in_qr_code_scan_guideline_top"
+        app:layout_constraintEnd_toStartOf="@id/guideline_end"
+        app:layout_constraintStart_toStartOf="@id/guideline_end"
+        app:layout_constraintTop_toTopOf="@id/check_in_qr_code_scan_guideline_top" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/check_in_qr_code_scan_guideline_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_begin="@dimen/spacing_normal" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/check_in_qr_code_scan_guideline_center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.5" />
+
+    <include layout="@layout/merge_guidelines_side" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/menu/menu_bottom_nav.xml b/Corona-Warn-App/src/main/res/menu/menu_bottom_nav.xml
index 3c598525006aed7ca597de2bd57c5cc3a1b55016..bef4a123a6e4f1390db724399037facf57931f01 100644
--- a/Corona-Warn-App/src/main/res/menu/menu_bottom_nav.xml
+++ b/Corona-Warn-App/src/main/res/menu/menu_bottom_nav.xml
@@ -6,6 +6,11 @@
         android:icon="@drawable/ic_nav_home"
         android:title="@string/bottom_nav_home_title" />
 
+    <item
+        android:id="@+id/trace_location_attendee_nav_graph"
+        android:icon="@drawable/ic_nav_check_in"
+        android:title="@string/bottom_nav_check_ins_title" />
+
     <item
         android:id="@+id/contact_diary_nav_graph"
         android:icon="@drawable/ic_nav_diary"
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 3f864197f4d746404191f1e46ff49d39f8d020f4..cc6efcc8ebfc90c536d5222489f7c5cd8e9ba67c 100644
--- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
@@ -9,6 +9,9 @@
     <include app:graph="@navigation/test_nav_graph" />
     <!--    Contact Diary graph-->
     <include app:graph="@navigation/contact_diary_nav_graph" />
+    <!--    Event registration graphs-->
+    <include app:graph="@navigation/trace_location_organizer_nav_graph" />
+    <include app:graph="@navigation/trace_location_attendee_nav_graph" />
 
     <!-- Main -->
     <fragment
@@ -545,8 +548,7 @@
         android:id="@+id/surveyConsentDetailFragment"
         android:name="de.rki.coronawarnapp.datadonation.survey.consent.SurveyConsentDetailFragment"
         android:label="survey_consent_detail_fragment"
-        tools:layout="@layout/survey_consent_detail_fragment">
-    </fragment>
+        tools:layout="@layout/survey_consent_detail_fragment" />
     <fragment
         android:id="@+id/analyticsUserInputFragment"
         android:name="de.rki.coronawarnapp.datadonation.analytics.ui.input.AnalyticsUserInputFragment"
@@ -572,8 +574,7 @@
         android:id="@+id/ppaMoreInfoFragment"
         android:name="de.rki.coronawarnapp.datadonation.analytics.ui.PpaMoreInfoFragment"
         android:label="PpaMoreInfoFragment"
-        tools:layout="@layout/fragment_ppa_more_info">
-    </fragment>
+        tools:layout="@layout/fragment_ppa_more_info" />
     <fragment
         android:id="@+id/settingsPrivacyPreservingAnalyticsFragment"
         android:name="de.rki.coronawarnapp.ui.settings.analytics.SettingsPrivacyPreservingAnalyticsFragment"
@@ -595,7 +596,7 @@
         android:id="@+id/debugLogUploadFragment"
         android:name="de.rki.coronawarnapp.bugreporting.debuglog.ui.upload.DebugLogUploadFragment"
         android:label="DebugLogUploadFragment"
-        tools:layout="@layout/bugreporting_debuglog_upload_fragment" >
+        tools:layout="@layout/bugreporting_debuglog_upload_fragment">
         <action
             android:id="@+id/action_debugLogUploadFragment_to_debugLogLegalFragment"
             app:destination="@id/debugLogLegalFragment" />
diff --git a/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml
new file mode 100644
index 0000000000000000000000000000000000000000..062c8064c0103be7b5f6b29c49ba31bcd12702f3
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/trace_location_attendee_nav_graph"
+    app:startDestination="@id/checkInsFragment">
+    <fragment
+        android:id="@+id/confirmCheckInFragment"
+        android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInFragment"
+        android:label="fragment_confirm_check_in"
+        tools:layout="@layout/fragment_confirm_check_in">
+        <argument
+            android:name="traceLocation"
+            app:argType="de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.VerifiedTraceLocation" />
+    </fragment>
+    <fragment
+        android:id="@+id/scanCheckInQrCodeFragment"
+        android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeFragment"
+        android:label="ScanCheckInQrCodeFragment"
+        tools:layout="@layout/fragment_scan_check_in_qr_code" />
+
+    <fragment
+        android:id="@+id/checkInsFragment"
+        android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment"
+        android:label="CheckInsFragment"
+        tools:layout="@layout/fragment_check_ins">
+        <deepLink app:uri="coronawarnapp://check-ins/{uri}" />
+        <action
+            android:id="@+id/action_checkInsFragment_to_scanCheckInQrCodeFragment"
+            app:destination="@id/scanCheckInQrCodeFragment" />
+        <action
+            android:id="@+id/action_checkInsFragment_to_confirmCheckInFragment"
+            app:destination="@id/confirmCheckInFragment" />
+        <argument
+            android:name="uri"
+            android:defaultValue="@null"
+            app:argType="string"
+            app:nullable="true" />
+    </fragment>
+
+</navigation>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml
new file mode 100644
index 0000000000000000000000000000000000000000..35744089a2654cb434804ecdceb6fc1bb55090bb
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/event_organizer_nav_graph">
+    <!-- TODO add organiser screens -->
+</navigation>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml
index 6e6d5106ce8aac361f155b78f0b1aa73f19296d9..2285da38b5227a2efaafe21ad36783185e3e2148 100644
--- a/Corona-Warn-App/src/main/res/values-de/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/strings.xml
@@ -1802,6 +1802,8 @@
     <string name="bottom_nav_home_title">Startseite</string>
     <!-- XHED: Title for BottomNav diary screen title -->
     <string name="bottom_nav_diary_title">Tagebuch</string>
+    <!-- XHED: Title for BottomNav check-in screen title -->
+    <string name="bottom_nav_check_ins_title">Check In</string>
 
     <!-- ####################################
            Data Donation & Survey
@@ -1919,4 +1921,8 @@
     <string name="duration_dialog_ok_button">OK</string>
     <!-- NOTR -->
     <string name="duration_dialog_default_value">00:00</string>
+
+    <!-- Scan check in QR Code-->
+    <!-- XTXT: Scan check in QR-Code  FAB text-->
+    <string name="scan_check_in_qr_code">QR-Code scannen</string>
 </resources>
diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml
index 38eb2464b2fe251d64f1b1f0568a845cb2392325..75d293fde8e1638f48c051b11733c1cbbc02263a 100644
--- a/Corona-Warn-App/src/main/res/values/strings.xml
+++ b/Corona-Warn-App/src/main/res/values/strings.xml
@@ -1811,6 +1811,8 @@
     <string name="bottom_nav_home_title">"Start Screen"</string>
     <!-- XHED: Title for BottomNav diary screen title -->
     <string name="bottom_nav_diary_title">"Journal"</string>
+    <!-- XHED: Title for BottomNav check-in screen title -->
+    <string name="bottom_nav_check_ins_title">Check In</string>
 
     <!-- ####################################
            Data Donation & Survey
@@ -1928,4 +1930,8 @@
     <string name="duration_dialog_ok_button">"OK"</string>
     <!-- NOTR -->
     <string name="duration_dialog_default_value">"00:00"</string>
+
+    <!-- Scan check in QR Code-->
+    <!-- XTXT: Scan check in QR-Code  FAB text-->
+    <string name="scan_check_in_qr_code">Scan QR-Code</string>
 </resources>
diff --git a/Corona-Warn-App/src/main/res/xml/provider_paths.xml b/Corona-Warn-App/src/main/res/xml/provider_paths.xml
index cc1e648e7f702573ec5da2f64b42a4ed4a409fb0..104e4132425efe495666ece2264a7b4e24aa5dc4 100644
--- a/Corona-Warn-App/src/main/res/xml/provider_paths.xml
+++ b/Corona-Warn-App/src/main/res/xml/provider_paths.xml
@@ -6,4 +6,7 @@
     <cache-path
         name="shared_logs"
         path="debuglog/shared/" />
+    <files-path
+        name="Events"
+        path="events/" />
 </paths>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/Base32Test.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/Base32Test.kt
new file mode 100644
index 0000000000000000000000000000000000000000..674f4b7654f169f9c8ab7f44a74b914c9b958768
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/Base32Test.kt
@@ -0,0 +1,50 @@
+package de.rki.coronawarnapp.eventregistration
+
+import de.rki.coronawarnapp.eventregistration.common.base32
+import de.rki.coronawarnapp.eventregistration.common.decodeBase32
+import io.kotest.matchers.shouldBe
+import okio.ByteString.Companion.toByteString
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+import testhelpers.BaseTest
+
+class Base32Test : BaseTest() {
+
+    @ParameterizedTest
+    @ArgumentsSource(NoPaddingTestProvider::class)
+    fun `Encoding with base32NoPadding`(input: String, expected: String) {
+        input.base32(padding = false) shouldBe expected
+    }
+
+    @ParameterizedTest
+    @ArgumentsSource(WithPaddingTestProvider::class)
+    fun `Encoding with base32WithPadding`(input: String, expected: String) {
+        input.base32() shouldBe expected
+    }
+
+    @ParameterizedTest
+    @ArgumentsSource(NoPaddingTestProvider::class)
+    fun `Encoding ByteString with base32NoPadding`(input: String, expected: String) {
+        val byteString = input.toByteArray().toByteString()
+        byteString.base32(padding = false) shouldBe expected
+    }
+
+    @ParameterizedTest
+    @ArgumentsSource(WithPaddingTestProvider::class)
+    fun `Encoding ByteString with base32WithPadding`(input: String, expected: String) {
+        val byteString = input.toByteArray().toByteString()
+        byteString.base32() shouldBe expected
+    }
+
+    @ParameterizedTest
+    @ArgumentsSource(WithPaddingTestProvider::class)
+    fun `Decoding with base32WithPadding ByteString`(expected: String, input: String) {
+        input.decodeBase32().string(Charsets.UTF_8) shouldBe expected
+    }
+
+    @ParameterizedTest
+    @ArgumentsSource(NoPaddingTestProvider::class)
+    fun `Decoding with base32NoPadding ByteString`(expected: String, input: String) {
+        input.decodeBase32().string(Charsets.UTF_8) shouldBe expected
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/NoPaddingTestProvider.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/NoPaddingTestProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..69f2287a9c2b998162cac5e059fcf592345341e4
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/NoPaddingTestProvider.kt
@@ -0,0 +1,33 @@
+package de.rki.coronawarnapp.eventregistration
+
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
+import java.util.stream.Stream
+
+class NoPaddingTestProvider : ArgumentsProvider {
+    override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+        return Stream.of(
+            Arguments.of(
+                "the quick brown fox jumps over the lazy dog",
+                "ORUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JAMRXWO"
+            ),
+            Arguments.of(
+                "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG",
+                "KREEKICRKVEUGSZAIJJE6V2OEBDE6WBAJJKU2UCTEBHVMRKSEBKEQRJAJRAVUWJAIRHUO"
+            ),
+            Arguments.of(
+                "die heiße zypernsonne quälte max und victoria ja böse auf dem weg bis zur küste.",
+                "MRUWKIDIMVU4HH3FEB5HS4DFOJXHG33ONZSSA4LVYOSGY5DFEBWWC6BAOVXGIIDWNFRXI33SNFQSA2TBEBRMHNTTMUQGC5LGEBSGK3JAO5SWOIDCNFZSA6TVOIQGXQ54ON2GKLQ"
+            ),
+            Arguments.of(
+                "DIE HEISSE ZYPERNSONNE QUÄLTE MAX UND VICTORIA JA BÖSE AUF DEM WEG BIS ZUR KÜSTE.",
+                "IREUKICIIVEVGU2FEBNFSUCFKJHFGT2OJZCSAUKVYOCEYVCFEBGUCWBAKVHEIICWJFBVIT2SJFASASSBEBBMHFSTIUQECVKGEBCEKTJAK5CUOICCJFJSAWSVKIQEXQ44KNKEKLQ"
+            ),
+            Arguments.of(
+                "Hello World!",
+                "JBSWY3DPEBLW64TMMQQQ"
+            ),
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/WithPaddingTestProvider.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/WithPaddingTestProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c54ed7c290fc4c4267f12ff54535f2603b1f473c
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/WithPaddingTestProvider.kt
@@ -0,0 +1,33 @@
+package de.rki.coronawarnapp.eventregistration
+
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
+import java.util.stream.Stream
+
+class WithPaddingTestProvider : ArgumentsProvider {
+    override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+        return Stream.of(
+            Arguments.of(
+                "the quick brown fox jumps over the lazy dog",
+                "ORUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JAMRXWO==="
+            ),
+            Arguments.of(
+                "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG",
+                "KREEKICRKVEUGSZAIJJE6V2OEBDE6WBAJJKU2UCTEBHVMRKSEBKEQRJAJRAVUWJAIRHUO==="
+            ),
+            Arguments.of(
+                "die heiße zypernsonne quälte max und victoria ja böse auf dem weg bis zur küste.",
+                "MRUWKIDIMVU4HH3FEB5HS4DFOJXHG33ONZSSA4LVYOSGY5DFEBWWC6BAOVXGIIDWNFRXI33SNFQSA2TBEBRMHNTTMUQGC5LGEBSGK3JAO5SWOIDCNFZSA6TVOIQGXQ54ON2GKLQ="
+            ),
+            Arguments.of(
+                "DIE HEISSE ZYPERNSONNE QUÄLTE MAX UND VICTORIA JA BÖSE AUF DEM WEG BIS ZUR KÜSTE.",
+                "IREUKICIIVEVGU2FEBNFSUCFKJHFGT2OJZCSAUKVYOCEYVCFEBGUCWBAKVHEIICWJFBVIT2SJFASASSBEBBMHFSTIUQECVKGEBCEKTJAK5CUOICCJFJSAWSVKIQEXQ44KNKEKLQ="
+            ),
+            Arguments.of(
+                "Hello World!",
+                "JBSWY3DPEBLW64TMMQQQ===="
+            ),
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/attendee/confirm/ConfirmCheckInViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/attendee/confirm/ConfirmCheckInViewModelTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1bb9410f2eeb38a6b5308ec6d19b52b06e144fd1
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/attendee/confirm/ConfirmCheckInViewModelTest.kt
@@ -0,0 +1,35 @@
+package de.rki.coronawarnapp.eventregistration.attendee.confirm
+
+import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInNavigation
+import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInViewModel
+import io.kotest.matchers.shouldBe
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.BaseTest
+import testhelpers.extensions.InstantExecutorExtension
+import testhelpers.extensions.getOrAwaitValue
+
+@ExtendWith(InstantExecutorExtension::class)
+class ConfirmCheckInViewModelTest : BaseTest() {
+
+    private lateinit var viewModel: ConfirmCheckInViewModel
+
+    @BeforeEach
+    fun setUp() {
+        viewModel = ConfirmCheckInViewModel()
+    }
+
+    @Test
+    fun onClose() {
+        viewModel.onClose()
+        viewModel.events.getOrAwaitValue() shouldBe ConfirmCheckInNavigation.BackNavigation
+    }
+
+    @Test
+    fun onConfirmEvent() {
+        viewModel.onConfirmTraceLocation()
+        viewModel.events.getOrAwaitValue() shouldBe ConfirmCheckInNavigation.ConfirmNavigation
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidUrlProvider.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidUrlProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..62e33419136ee48800c5d1ecd84315d289b4ed36
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/InvalidUrlProvider.kt
@@ -0,0 +1,32 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
+import java.util.stream.Stream
+
+class InvalidUrlProvider : ArgumentsProvider {
+    override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+        return Stream.of(
+            Arguments.of(
+                "https://e.coronawarn.app/e1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBO" +
+                    "J2HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFFBU2" +
+                    "SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI="
+            ),
+            Arguments.of(
+                "https://e.coronawarn.app/c1/SINGED_ENCODED_LOCATION"
+            ),
+            Arguments.of(
+                "HTTPS://E.CORONAWARN.APP/C1/BIPEY33SMVWSA2LQON2W2IDEN5WG64RAONUXIIDBNVSXILBAMNXRBCM4UQARRKM6UQASAHR" +
+                    "KCC7CTDWGQ4JCO7RVZSWVIMQK4UPA.GBCAEIA7TEORBTUA25QHBOCWT26BCA5PORBS2E4FFWMJ3UU3P6SXOL" +
+                    "7SHUBCA7UEZBDDQ2R6VRJH7WBJKVF7GZYJA6YMRN27IPEP7NKGGJSWX3XQ"
+            ),
+            Arguments.of(
+                "HTTPS://E.CORONAWARN.APP/C1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBO" +
+                    "J2HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFFBU2" +
+                    "SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI"
+            ),
+            Arguments.of("")
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidatorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d0f30a69b74d3aba0aa7db9aae6fc6ebdb95de49
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/UriValidatorTest.kt
@@ -0,0 +1,29 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.matchers.shouldBe
+import org.junit.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ArgumentsSource
+
+class UriValidatorTest {
+
+    @ParameterizedTest
+    @ArgumentsSource(ValidUrlProvider::class)
+    fun `Valid URLs`(input: String) {
+        input.isValidQRCodeUri() shouldBe true
+    }
+
+    @ParameterizedTest
+    @ArgumentsSource(InvalidUrlProvider::class)
+    fun `Invalid URLs`(input: String) {
+        input.isValidQRCodeUri() shouldBe false
+    }
+
+    @Test
+    fun `Invalid  URL string`() {
+        shouldThrow<IllegalArgumentException> {
+            "Hello World!".isValidQRCodeUri()
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidUrlProvider.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidUrlProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6b8b83fbe79725ba9382671da81673ba7dd729c6
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/ValidUrlProvider.kt
@@ -0,0 +1,23 @@
+package de.rki.coronawarnapp.eventregistration.checkins.qrcode
+
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
+import java.util.stream.Stream
+
+class ValidUrlProvider : ArgumentsProvider {
+    override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
+        return Stream.of(
+            Arguments.of(
+                "HTTPS://E.CORONAWARN.APP/C1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBO" +
+                    "J2HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFFBU2" +
+                    "SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI="
+            ),
+            Arguments.of(
+                "https://e.coronawarn.app/c1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBO" +
+                    "J2HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFFBU2" +
+                    "SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI="
+            )
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocationKtTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocationKtTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7aefa48ae7e14c6714a058884d6b3c1747d9cb24
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocationKtTest.kt
@@ -0,0 +1,111 @@
+package de.rki.coronawarnapp.eventregistration.events
+
+import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity
+import io.kotest.matchers.shouldBe
+import org.joda.time.Instant
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+internal class DefaultTraceLocationKtTest : BaseTest() {
+
+    @Test
+    fun `toTraceLocation() should map to correct object when providing all arguments`() {
+        TraceLocationEntity(
+            guid = "TestGuid",
+            version = 1,
+            type = TraceLocation.Type.TEMPORARY_OTHER,
+            description = "TestTraceLocation",
+            address = "TestTraceLocationAddress",
+            startDate = Instant.parse("2021-01-01T12:00:00.000Z"),
+            endDate = Instant.parse("2021-01-01T18:00:00.000Z"),
+            defaultCheckInLengthInMinutes = 15,
+            signature = "signature"
+        ).toTraceLocation() shouldBe DefaultTraceLocation(
+            guid = "TestGuid",
+            version = 1,
+            type = TraceLocation.Type.TEMPORARY_OTHER,
+            description = "TestTraceLocation",
+            address = "TestTraceLocationAddress",
+            startDate = Instant.parse("2021-01-01T12:00:00.000Z"),
+            endDate = Instant.parse("2021-01-01T18:00:00.000Z"),
+            defaultCheckInLengthInMinutes = 15,
+            signature = "signature"
+        )
+    }
+
+    @Test
+    fun `toTraceLocation() should map to correct object when providing only arguments that are required`() {
+        TraceLocationEntity(
+            guid = "TestGuid",
+            version = 1,
+            type = TraceLocation.Type.PERMANENT_OTHER,
+            description = "TestTraceLocation",
+            address = "TestTraceLocationAddress",
+            startDate = null,
+            endDate = null,
+            defaultCheckInLengthInMinutes = null,
+            signature = "signature"
+        ).toTraceLocation() shouldBe DefaultTraceLocation(
+            guid = "TestGuid",
+            version = 1,
+            type = TraceLocation.Type.PERMANENT_OTHER,
+            description = "TestTraceLocation",
+            address = "TestTraceLocationAddress",
+            startDate = null,
+            endDate = null,
+            defaultCheckInLengthInMinutes = null,
+            signature = "signature"
+        )
+    }
+
+    @Test
+    fun `toTraceLocations() should map a list of TraceLocationEntities correctly`() {
+        listOf(
+            TraceLocationEntity(
+                guid = "TestGuid1",
+                version = 1,
+                type = TraceLocation.Type.TEMPORARY_OTHER,
+                description = "TestTraceLocation1",
+                address = "TestTraceLocationAddress1",
+                startDate = Instant.parse("2021-01-01T12:00:00.000Z"),
+                endDate = Instant.parse("2021-01-01T18:00:00.000Z"),
+                defaultCheckInLengthInMinutes = 15,
+                signature = "signature"
+            ),
+            TraceLocationEntity(
+                guid = "TestGuid2",
+                version = 1,
+                type = TraceLocation.Type.PERMANENT_OTHER,
+                description = "TestTraceLocation2",
+                address = "TestTraceLocationAddress2",
+                startDate = null,
+                endDate = null,
+                defaultCheckInLengthInMinutes = null,
+                signature = "signature"
+            )
+        ).toTraceLocations() shouldBe listOf(
+            DefaultTraceLocation(
+                guid = "TestGuid1",
+                version = 1,
+                type = TraceLocation.Type.TEMPORARY_OTHER,
+                description = "TestTraceLocation1",
+                address = "TestTraceLocationAddress1",
+                startDate = Instant.parse("2021-01-01T12:00:00.000Z"),
+                endDate = Instant.parse("2021-01-01T18:00:00.000Z"),
+                defaultCheckInLengthInMinutes = 15,
+                signature = "signature"
+            ),
+            DefaultTraceLocation(
+                guid = "TestGuid2",
+                version = 1,
+                type = TraceLocation.Type.PERMANENT_OTHER,
+                description = "TestTraceLocation2",
+                address = "TestTraceLocationAddress2",
+                startDate = null,
+                endDate = null,
+                defaultCheckInLengthInMinutes = null,
+                signature = "signature"
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConvertersTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConvertersTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b11ff01460ae53187c857905f50fe49f7e239a0c
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConvertersTest.kt
@@ -0,0 +1,29 @@
+package de.rki.coronawarnapp.eventregistration.storage.entity
+
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import io.kotest.matchers.shouldBe
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+internal class TraceLocationConvertersTest : BaseTest() {
+
+    private val converter = TraceLocationConverters()
+
+    @Test
+    fun `toTraceLocationType() should convert different integer values to correct TraceLocation Types`() {
+        with(converter) {
+            toTraceLocationType(0) shouldBe TraceLocation.Type.UNSPECIFIED
+            toTraceLocationType(1) shouldBe TraceLocation.Type.PERMANENT_OTHER
+            toTraceLocationType(2) shouldBe TraceLocation.Type.TEMPORARY_OTHER
+        }
+    }
+
+    @Test
+    fun `fromTraceLocationType() should convert TraceLocation Types to correct integer values`() {
+        with(converter) {
+            fromTraceLocationType(TraceLocation.Type.UNSPECIFIED) shouldBe 0
+            fromTraceLocationType(TraceLocation.Type.PERMANENT_OTHER) shouldBe 1
+            fromTraceLocationType(TraceLocation.Type.TEMPORARY_OTHER) shouldBe 2
+        }
+    }
+}
\ No newline at end of file
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntityTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntityTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e796c1838e3def3d812cdad83e767645c88b016b
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntityTest.kt
@@ -0,0 +1,61 @@
+package de.rki.coronawarnapp.eventregistration.storage.entity
+
+import de.rki.coronawarnapp.eventregistration.events.DefaultTraceLocation
+import de.rki.coronawarnapp.eventregistration.events.TraceLocation
+import io.kotest.matchers.shouldBe
+import org.joda.time.Instant
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+internal class TraceLocationEntityTest : BaseTest() {
+
+    @Test
+    fun `toTraceLocationEntity() should map to TraceLocationEntity correctly with all arguments`() {
+        DefaultTraceLocation(
+            guid = "TestGuid",
+            version = 1,
+            type = TraceLocation.Type.TEMPORARY_OTHER,
+            description = "TestTraceLocation",
+            address = "TestTraceLocationAddress",
+            startDate = Instant.parse("2021-01-01T12:00:00.000Z"),
+            endDate = Instant.parse("2021-01-01T18:00:00.000Z"),
+            defaultCheckInLengthInMinutes = 15,
+            signature = "signature"
+        ).toTraceLocationEntity() shouldBe TraceLocationEntity(
+            guid = "TestGuid",
+            version = 1,
+            type = TraceLocation.Type.TEMPORARY_OTHER,
+            description = "TestTraceLocation",
+            address = "TestTraceLocationAddress",
+            startDate = Instant.parse("2021-01-01T12:00:00.000Z"),
+            endDate = Instant.parse("2021-01-01T18:00:00.000Z"),
+            defaultCheckInLengthInMinutes = 15,
+            signature = "signature"
+        )
+    }
+
+    @Test
+    fun `toTraceLocationEntity() should map to TraceLocationEntity correctly with some arguments as null`() {
+        DefaultTraceLocation(
+            guid = "TestGuid",
+            version = 1,
+            type = TraceLocation.Type.PERMANENT_OTHER,
+            description = "TestTraceLocation",
+            address = "TestTraceLocationAddress",
+            startDate = null,
+            endDate = null,
+            defaultCheckInLengthInMinutes = null,
+            signature = "signature"
+        ).toTraceLocationEntity() shouldBe TraceLocationEntity(
+            guid = "TestGuid",
+            version = 1,
+            type = TraceLocation.Type.PERMANENT_OTHER,
+            description = "TestTraceLocation",
+            address = "TestTraceLocationAddress",
+            startDate = null,
+            endDate = null,
+            defaultCheckInLengthInMinutes = null,
+            signature = "signature"
+        )
+    }
+}
\ No newline at end of file
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeViewModelTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2dae524bbe0ed017588eb4f6de36e86b845573f7
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/scan/ScanCheckInQrCodeViewModelTest.kt
@@ -0,0 +1,42 @@
+package de.rki.coronawarnapp.ui.eventregistration.attendee.scan
+
+import com.google.zxing.Result
+import com.journeyapps.barcodescanner.BarcodeResult
+import io.kotest.matchers.shouldBe
+import io.mockk.every
+import io.mockk.mockk
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.BaseTest
+import testhelpers.extensions.InstantExecutorExtension
+import testhelpers.extensions.getOrAwaitValue
+
+@ExtendWith(InstantExecutorExtension::class)
+class ScanCheckInQrCodeViewModelTest : BaseTest() {
+
+    private lateinit var viewModel: ScanCheckInQrCodeViewModel
+
+    @BeforeEach
+    fun setup() {
+        viewModel = ScanCheckInQrCodeViewModel()
+    }
+
+    @Test
+    fun `onNavigateUp goes back`() {
+        viewModel.onNavigateUp()
+        viewModel.events.getOrAwaitValue() shouldBe ScanCheckInQrCodeNavigation.BackNavigation
+    }
+
+    @Test
+    fun `onScanResult results in navigation url`() {
+        val mockedResult = mockk<BarcodeResult>().apply {
+            every { result } returns mockk<Result>().apply {
+                every { text } returns "https://coronawarn.app/E1/SOME_PATH_GOES_HERE"
+            }
+        }
+        viewModel.onScanResult(mockedResult)
+        viewModel.events.getOrAwaitValue() shouldBe
+            ScanCheckInQrCodeNavigation.ScanResultNavigation("https://coronawarn.app/E1/SOME_PATH_GOES_HERE")
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/checkin/ConfirmCheckInViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/checkin/ConfirmCheckInViewModelTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391