VancedMicroG/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/MeasuredExposure.kt

147 lines
7.4 KiB
Kotlin

/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.nearby.exposurenotification
import android.util.Log
import com.google.android.gms.nearby.exposurenotification.*
import java.util.concurrent.TimeUnit
data class ExposureScanSummary(val time: Long, val rpis: Int, val records: Int)
data class PlainExposure(val rpi: ByteArray, val aem: ByteArray, val timestamp: Long, val duration: Long, val rssi: Int)
data class MeasuredExposure(val timestamp: Long, val duration: Long, val rssi: Int, val txPower: Int, @CalibrationConfidence val confidence: Int, val key: TemporaryExposureKey) {
val attenuation
get() = txPower - (rssi + currentDeviceInfo.rssiCorrection)
}
fun List<MeasuredExposure>.merge(): List<MergedExposure> {
val keys = map { it.key }.distinct()
val result = arrayListOf<MergedExposure>()
for (key in keys) {
var merged: MergedExposure? = null
for (exposure in filter { it.key == key }.distinctBy { it.timestamp }.sortedBy { it.timestamp }) {
if (merged != null && merged.timestamp + MergedExposure.MAXIMUM_DURATION > exposure.timestamp) {
merged += exposure
} else {
if (merged != null) {
result.add(merged)
}
merged = MergedExposure(key, exposure.timestamp, exposure.txPower, exposure.confidence, listOf(MergedSubExposure(exposure.attenuation, exposure.duration)))
}
if (merged.durationInMinutes >= 30) {
result.add(merged)
merged = null
}
}
if (merged != null) {
result.add(merged)
}
}
return result
}
data class MergedSubExposure(val attenuation: Int, val duration: Long)
data class MergedExposure internal constructor(val key: TemporaryExposureKey, val timestamp: Long, val txPower: Int, @CalibrationConfidence val confidence: Int, val subs: List<MergedSubExposure>) {
@RiskLevel
val transmissionRiskLevel: Int
get() = key.transmissionRiskLevel
val durationInMinutes
get() = TimeUnit.MILLISECONDS.toMinutes(subs.map { it.duration }.sum())
val daysSinceExposure
get() = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - timestamp)
val attenuation
get() = if (subs.map { it.duration }.sum() == 0L) subs[0].attenuation else (subs.map { it.attenuation * it.duration }.sum().toDouble() / subs.map { it.duration }.sum().toDouble()).toInt()
fun getAttenuationRiskScore(configuration: ExposureConfiguration): Int {
return when {
attenuation > 73 -> configuration.attenuationScores[0]
attenuation > 63 -> configuration.attenuationScores[1]
attenuation > 51 -> configuration.attenuationScores[2]
attenuation > 33 -> configuration.attenuationScores[3]
attenuation > 27 -> configuration.attenuationScores[4]
attenuation > 15 -> configuration.attenuationScores[5]
attenuation > 10 -> configuration.attenuationScores[6]
else -> configuration.attenuationScores[7]
}
}
fun getDaysSinceLastExposureRiskScore(configuration: ExposureConfiguration): Int {
return when {
daysSinceExposure >= 14 -> configuration.daysSinceLastExposureScores[0]
daysSinceExposure >= 12 -> configuration.daysSinceLastExposureScores[1]
daysSinceExposure >= 10 -> configuration.daysSinceLastExposureScores[2]
daysSinceExposure >= 8 -> configuration.daysSinceLastExposureScores[3]
daysSinceExposure >= 6 -> configuration.daysSinceLastExposureScores[4]
daysSinceExposure >= 4 -> configuration.daysSinceLastExposureScores[5]
daysSinceExposure >= 2 -> configuration.daysSinceLastExposureScores[6]
else -> configuration.daysSinceLastExposureScores[7]
}
}
fun getDurationRiskScore(configuration: ExposureConfiguration): Int {
return when {
durationInMinutes == 0L -> configuration.durationScores[0]
durationInMinutes <= 5 -> configuration.durationScores[1]
durationInMinutes <= 10 -> configuration.durationScores[2]
durationInMinutes <= 15 -> configuration.durationScores[3]
durationInMinutes <= 20 -> configuration.durationScores[4]
durationInMinutes <= 25 -> configuration.durationScores[5]
durationInMinutes <= 30 -> configuration.durationScores[6]
else -> configuration.durationScores[7]
}
}
fun getTransmissionRiskScore(configuration: ExposureConfiguration): Int {
return when (transmissionRiskLevel) {
RiskLevel.RISK_LEVEL_LOWEST -> configuration.transmissionRiskScores[0]
RiskLevel.RISK_LEVEL_LOW -> configuration.transmissionRiskScores[1]
RiskLevel.RISK_LEVEL_LOW_MEDIUM -> configuration.transmissionRiskScores[2]
RiskLevel.RISK_LEVEL_MEDIUM -> configuration.transmissionRiskScores[3]
RiskLevel.RISK_LEVEL_MEDIUM_HIGH -> configuration.transmissionRiskScores[4]
RiskLevel.RISK_LEVEL_HIGH -> configuration.transmissionRiskScores[5]
RiskLevel.RISK_LEVEL_VERY_HIGH -> configuration.transmissionRiskScores[6]
RiskLevel.RISK_LEVEL_HIGHEST -> configuration.transmissionRiskScores[7]
else -> 1
}
}
fun getRiskScore(configuration: ExposureConfiguration): Int {
val risk = getAttenuationRiskScore(configuration) * getDaysSinceLastExposureRiskScore(configuration) * getDurationRiskScore(configuration) * getTransmissionRiskScore(configuration)
Log.d(TAG, "Risk score calculation: ${getAttenuationRiskScore(configuration)} * ${getDaysSinceLastExposureRiskScore(configuration)} * ${getDurationRiskScore(configuration)} * ${getTransmissionRiskScore(configuration)} = $risk")
if (risk < configuration.minimumRiskScore) return 0
return risk
}
fun getAttenuationDurations(configuration: ExposureConfiguration): IntArray {
return intArrayOf(
TimeUnit.MILLISECONDS.toMinutes(subs.filter { it.attenuation < configuration.durationAtAttenuationThresholds[0] }.map { it.duration }.sum()).toInt(),
TimeUnit.MILLISECONDS.toMinutes(subs.filter { it.attenuation >= configuration.durationAtAttenuationThresholds[0] && it.attenuation < configuration.durationAtAttenuationThresholds[1] }.map { it.duration }.sum()).toInt(),
TimeUnit.MILLISECONDS.toMinutes(subs.filter { it.attenuation >= configuration.durationAtAttenuationThresholds[1] }.map { it.duration }.sum()).toInt()
)
}
fun toExposureInformation(configuration: ExposureConfiguration): ExposureInformation =
ExposureInformation.ExposureInformationBuilder()
.setDateMillisSinceEpoch(key.rollingStartIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH_MS)
.setDurationMinutes(durationInMinutes.toInt())
.setAttenuationValue(attenuation)
.setTransmissionRiskLevel(transmissionRiskLevel)
.setTotalRiskScore(getRiskScore(configuration))
.setAttenuationDurations(getAttenuationDurations(configuration))
.build()
operator fun plus(exposure: MeasuredExposure): MergedExposure = copy(subs = subs + MergedSubExposure(exposure.attenuation, exposure.duration))
companion object {
const val MAXIMUM_DURATION = 30 * 60 * 1000
}
}