From 4937ffc2cd4107a1e1204cba00e8b0cfaa0f8782 Mon Sep 17 00:00:00 2001
From: Chilja Gossow <49635654+chiljamgossow@users.noreply.github.com>
Date: Tue, 16 Mar 2021 18:30:53 +0100
Subject: [PATCH] Test menu additions for download, matching, risk calculation
 (#2607)

* matching

* hook to ui

* add fake data sources

* string output

* lint

* test fragment

* merge main branch

* add toast

* klint

* detekt

* adaptation for split

* klint

* add local date

Co-authored-by: Mohamed Metwalli <mohamed.metwalli@sap.com>
Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com>
---
 .../ui/EventRegistrationTestFragment.kt       | 25 ++++++++
 .../EventRegistrationTestFragmentViewModel.kt | 15 ++++-
 .../fragment_test_eventregistration.xml       | 60 +++++++++++++++----
 .../EventRegistrationModule.kt                |  5 ++
 .../checkins/download/CheckInsPackage.kt      | 56 +++++++++++++++++
 .../download/DownloadedCheckInsRepo.kt        | 15 +++++
 .../riskcalculation/CheckInMatcher.kt         | 37 ++++++++++++
 .../checkins/riskcalculation/Matching.kt      | 38 ++++++++++++
 8 files changed, 239 insertions(+), 12 deletions(-)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/riskcalculation/CheckInMatcher.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/riskcalculation/Matching.kt

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
index 7ccb3476a..3138af290 100644
--- 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
@@ -3,6 +3,7 @@ package de.rki.coronawarnapp.test.eventregistration.ui
 import android.annotation.SuppressLint
 import android.os.Bundle
 import android.view.View
+import android.widget.Toast
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
 import de.rki.coronawarnapp.R
@@ -10,6 +11,7 @@ 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.observe2
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
@@ -49,6 +51,29 @@ class EventRegistrationTestFragment : Fragment(R.layout.fragment_test_eventregis
                 findNavController().navigate(R.id.showStoredEventsTestFragment)
             }
         }
+        binding.runMatcher.setOnClickListener {
+            Toast.makeText(context, "Not implemented", Toast.LENGTH_SHORT).show()
+        }
+
+        binding.downloadReportedCheckIns.setOnClickListener {
+            Toast.makeText(context, "Not implemented", Toast.LENGTH_SHORT).show()
+        }
+
+        binding.calculateRisk.setOnClickListener {
+            Toast.makeText(context, "Not implemented", Toast.LENGTH_SHORT).show()
+        }
+
+        viewModel.checkInOverlaps.observe2(this) {
+            val text = it.fold(StringBuilder()) { stringBuilder, checkInOverlap ->
+                stringBuilder
+                    .append("CheckIn Id ${checkInOverlap.checkInId}")
+                    .append("Date ${checkInOverlap.localDate}")
+                    .append("Min. ${checkInOverlap.overlap.standardMinutes}")
+                    .append("\n")
+            }
+            binding.resultText.text = text
+            binding.resultText.visibility = View.VISIBLE
+        }
     }
 
     companion object {
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
index 105a19bd4..1d26a529d 100644
--- 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
@@ -1,15 +1,28 @@
 package de.rki.coronawarnapp.test.eventregistration.ui
 
+import androidx.lifecycle.MutableLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.eventregistration.checkins.riskcalculation.CheckInMatcher
+import de.rki.coronawarnapp.eventregistration.checkins.riskcalculation.CheckInOverlap
 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
+    private val dispatcherProvider: DispatcherProvider,
+    private val checkInMatcher: CheckInMatcher
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
 
+    val checkInOverlaps = MutableLiveData<List<CheckInOverlap>>()
+
+    fun runMatcher() {
+        launch {
+            val overlaps = checkInMatcher.execute()
+            checkInOverlaps.postValue(overlaps)
+        }
+    }
+
     @AssistedFactory
     interface Factory : SimpleCWAViewModelFactory<EventRegistrationTestFragmentViewModel>
 }
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
index 390245bc4..86c86ce55 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml
@@ -10,17 +10,13 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_margin="8dp"
-        android:orientation="vertical"
-        android:paddingBottom="32dp">
+        android:orientation="vertical">
 
         <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
@@ -32,7 +28,7 @@
                 android:id="@+id/testQrCodeCreation"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="10dp"
+                android:layout_marginTop="@dimen/spacing_tiny"
                 android:text="QR Code Creation" />
 
         </LinearLayout>
@@ -41,10 +37,10 @@
             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:layout_marginHorizontal="@dimen/spacing_tiny"
+            android:layout_marginBottom="@dimen/spacing_tiny"
             android:orientation="vertical">
+
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
@@ -54,10 +50,54 @@
                 android:id="@+id/scanCheckInQrCode"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/spacing_tiny"
                 android:text="Scan check in QR code" />
 
         </LinearLayout>
 
+        <LinearLayout
+            style="@style/Card"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/spacing_tiny"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Download, matching &amp; risk calculation" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/downloadReportedCheckIns"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/spacing_tiny"
+                android:text="download check-ins" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/runMatcher"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/spacing_small"
+                android:text="Run matcher" />
+
+            <TextView
+                android:id="@+id/resultText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/spacing_small"
+                android:text="Matching result"
+                android:visibility="gone" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/calculateRisk"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/spacing_small"
+                android:text="Calculate risk" />
+
+        </LinearLayout>
+
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/event_container"
             style="@style/Card"
@@ -108,7 +148,5 @@
                 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/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt
index 2a0821367..82434300f 100644
--- 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
@@ -2,6 +2,8 @@ package de.rki.coronawarnapp.eventregistration
 
 import dagger.Binds
 import dagger.Module
+import de.rki.coronawarnapp.eventregistration.checkins.download.DownloadedCheckInsRepo
+import de.rki.coronawarnapp.eventregistration.checkins.download.FakeDownloadedCheckInsRepo
 import de.rki.coronawarnapp.eventregistration.checkins.qrcode.DefaultQRCodeVerifier
 import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier
 import de.rki.coronawarnapp.eventregistration.storage.repo.DefaultTraceLocationRepository
@@ -16,4 +18,7 @@ abstract class EventRegistrationModule {
     @Binds
     abstract fun traceLocationRepository(defaultTraceLocationRepo: DefaultTraceLocationRepository):
         TraceLocationRepository
+
+    @Binds
+    abstract fun downloadedCheckInsRepo(repository: FakeDownloadedCheckInsRepo): DownloadedCheckInsRepo
 }
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
index 97f513181..64a3ccf60 100644
--- 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
@@ -1,6 +1,8 @@
 package de.rki.coronawarnapp.eventregistration.checkins.download
 
 import de.rki.coronawarnapp.eventregistration.checkins.CheckIn
+import org.joda.time.DateTime
+import org.joda.time.Instant
 
 interface CheckInsPackage {
 
@@ -9,3 +11,57 @@ interface CheckInsPackage {
      */
     suspend fun extractCheckIns(): List<CheckIn>
 }
+
+// TODO replace with actual implementation
+
+// proprietary dummy implementations
+object DummyCheckInPackage : CheckInsPackage {
+    override suspend fun extractCheckIns(): List<CheckIn> {
+        return listOf(dummyEventCheckIn1)
+    }
+}
+
+private const val TYPE_LOCATION = 1
+private const val TYPE_EVENT = 2
+
+private val dummyEventCheckIn1: CheckIn = CheckIn(
+    id = 1L,
+    guid = "eventOne",
+    version = 1,
+    type = TYPE_LOCATION,
+    description = "Restaurant",
+    address = "Around the corner",
+    traceLocationStart = null,
+    traceLocationEnd = null,
+    defaultCheckInLengthInMinutes = null,
+    signature = "signature",
+    checkInStart = Instant.ofEpochMilli(
+        DateTime(2021, 2, 2012, 11, 45).millis
+    ),
+    checkInEnd = Instant.ofEpochMilli(
+        DateTime(2021, 2, 20, 12, 15).millis
+    ),
+    targetCheckInEnd = null,
+    createJournalEntry = false
+)
+
+private val dummyEventCheckIn2: CheckIn = CheckIn(
+    id = 1L,
+    guid = "eventOne",
+    version = 1,
+    type = TYPE_EVENT,
+    description = "Women in tech meetup",
+    address = "Technology Park",
+    traceLocationStart = null,
+    traceLocationEnd = null,
+    defaultCheckInLengthInMinutes = null,
+    signature = "signature2",
+    checkInStart = Instant.ofEpochMilli(
+        DateTime(2021, 3, 20, 18, 45).millis
+    ),
+    checkInEnd = Instant.ofEpochMilli(
+        DateTime(2021, 3, 20, 20, 15).millis
+    ),
+    targetCheckInEnd = null,
+    createJournalEntry = false
+)
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
index 748cee114..a2fd5a465 100644
--- 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
@@ -1,6 +1,8 @@
 package de.rki.coronawarnapp.eventregistration.checkins.download
 
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import javax.inject.Inject
 
 interface DownloadedCheckInsRepo {
 
@@ -10,3 +12,16 @@ interface DownloadedCheckInsRepo {
 
     fun removeCheckIns(checkins: List<CheckInsPackage>)
 }
+
+class FakeDownloadedCheckInsRepo @Inject constructor() : DownloadedCheckInsRepo {
+    override val allCheckInsPackages: Flow<List<CheckInsPackage>>
+        get() = listOf(listOf<CheckInsPackage>(DummyCheckInPackage)).asFlow()
+
+    override fun addCheckIns(checkins: List<CheckInsPackage>) {
+        // TODO("Not yet implemented")
+    }
+
+    override fun removeCheckIns(checkins: List<CheckInsPackage>) {
+        // TODO("Not yet implemented")
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/riskcalculation/CheckInMatcher.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/riskcalculation/CheckInMatcher.kt
new file mode 100644
index 000000000..30b2f9e00
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/riskcalculation/CheckInMatcher.kt
@@ -0,0 +1,37 @@
+package de.rki.coronawarnapp.eventregistration.checkins.riskcalculation
+
+import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository
+import de.rki.coronawarnapp.eventregistration.checkins.download.DownloadedCheckInsRepo
+import kotlinx.coroutines.flow.firstOrNull
+import javax.inject.Inject
+
+class CheckInMatcher @Inject constructor(
+    private val checkInsRepository: CheckInRepository,
+    private val downloadedCheckInsRepo: DownloadedCheckInsRepo
+) {
+
+    suspend fun execute(): List<CheckInOverlap> {
+        val localCheckIns = checkInsRepository.allCheckIns.firstOrNull() ?: return emptyList()
+        val downloadedPackages = downloadedCheckInsRepo.allCheckInsPackages.firstOrNull() ?: return emptyList()
+        val relevantDownloadedCheckIns =
+            downloadedPackages.flatMap {
+                filterRelevantEventCheckIns(
+                    localCheckIns,
+                    it
+                )
+            }
+        if (relevantDownloadedCheckIns.isEmpty()) return emptyList()
+
+        // TODO split by midnight UTC?
+
+        // calculate time overlap
+        val eventOverlapList = mutableListOf<CheckInOverlap>()
+        relevantDownloadedCheckIns.forEach { relevantDownloadedCheckIn ->
+            localCheckIns.forEach { localCheckIn ->
+                val overlap = calculateOverlap(localCheckIn, relevantDownloadedCheckIn)
+                if (overlap != null) eventOverlapList.add(overlap)
+            }
+        }
+        return eventOverlapList
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/riskcalculation/Matching.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/riskcalculation/Matching.kt
new file mode 100644
index 000000000..79077c690
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/riskcalculation/Matching.kt
@@ -0,0 +1,38 @@
+package de.rki.coronawarnapp.eventregistration.checkins.riskcalculation
+
+import de.rki.coronawarnapp.eventregistration.checkins.CheckIn
+import de.rki.coronawarnapp.eventregistration.checkins.download.CheckInsPackage
+import org.joda.time.Duration
+import org.joda.time.Instant
+import org.joda.time.LocalDate
+
+suspend fun filterRelevantEventCheckIns(
+    localCheckIns: List<CheckIn>,
+    checkInsPackage: CheckInsPackage
+): List<CheckIn> {
+    val reportedCheckIns = checkInsPackage.extractCheckIns()
+    return reportedCheckIns.filter { reported ->
+        localCheckIns.find { local ->
+            reported.guid == local.guid
+        } != null
+    }
+}
+
+fun calculateOverlap(
+    local: CheckIn,
+    reported: CheckIn
+): CheckInOverlap? {
+    if (local.guid != reported.guid) return null
+    // TODO implement calculation
+    return null
+}
+
+data class CheckInOverlap(
+    val checkInId: Long,
+    val localDate: LocalDate,
+    val overlap: Duration
+)
+
+fun min(first: Instant, second: Instant) = Instant(kotlin.math.min(first.millis, second.millis))
+
+fun max(first: Instant, second: Instant) = Instant(kotlin.math.max(first.millis, second.millis))
-- 
GitLab