mirror of https://github.com/YTVanced/VancedMicroG
EN: Make internal structures closer to ExposureWindow mode
This commit is contained in:
parent
ec877f7a53
commit
876e32acd5
|
@ -12,6 +12,7 @@
|
|||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="com.google.android.gms.nearby.exposurenotification.EXPOSURE_CALLBACK" />
|
||||
|
||||
<application>
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ class AdvertiserService : LifecycleService() {
|
|||
private var looping = false
|
||||
private var callback: AdvertiseCallback? = null
|
||||
private val advertiser: BluetoothLeAdvertiser?
|
||||
get() = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser
|
||||
get() = BluetoothAdapter.getDefaultAdapter()?.bluetoothLeAdvertiser
|
||||
private lateinit var database: ExposureDatabase
|
||||
private val trigger = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
|
|
|
@ -12,15 +12,11 @@ import android.database.sqlite.SQLiteCursor
|
|||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.database.sqlite.SQLiteStatement
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||
import org.microg.gms.common.PackageUtils
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.concurrent.Future
|
||||
|
@ -224,7 +220,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||
val keys = listDiagnosisKeysPendingSearch(packageName, token)
|
||||
val oldestRpi = oldestRpi
|
||||
for (key in keys) {
|
||||
if (oldestRpi == null || key.rollingStartIntervalNumber * ROLLING_WINDOW_LENGTH_MS - ALLOWED_KEY_OFFSET_MS < oldestRpi) {
|
||||
if (oldestRpi == null || (key.rollingStartIntervalNumber + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS < oldestRpi) {
|
||||
// Early ignore because key is older than since we started scanning.
|
||||
applyDiagnosisKeySearchResult(key, false)
|
||||
} else {
|
||||
|
@ -250,50 +246,48 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||
}
|
||||
|
||||
fun findMeasuredExposures(key: TemporaryExposureKey): List<MeasuredExposure> {
|
||||
val list = arrayListOf<MeasuredExposure>()
|
||||
val allRpis = key.generateAllRpiIds()
|
||||
val rpis = (0 until key.rollingPeriod).map { i ->
|
||||
val pos = i * 16
|
||||
allRpis.sliceArray(pos until (pos + 16))
|
||||
}
|
||||
val measures = findMeasuredExposures(rpis, key.rollingStartIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH_MS - ALLOWED_KEY_OFFSET_MS, (key.rollingStartIntervalNumber.toLong() + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS)
|
||||
measures.filter {
|
||||
val index = rpis.indexOf(it.rpi)
|
||||
val measures = findExposures(rpis, key.rollingStartIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH_MS - ALLOWED_KEY_OFFSET_MS, (key.rollingStartIntervalNumber.toLong() + key.rollingPeriod) * ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS)
|
||||
return measures.filter {
|
||||
val index = rpis.indexOfFirst { rpi -> rpi.contentEquals(it.rpi) }
|
||||
val targetTimestamp = (key.rollingStartIntervalNumber + index).toLong() * ROLLING_WINDOW_LENGTH_MS
|
||||
it.timestamp > targetTimestamp - ALLOWED_KEY_OFFSET_MS && it.timestamp < targetTimestamp + ALLOWED_KEY_OFFSET_MS
|
||||
it.timestamp >= targetTimestamp - ALLOWED_KEY_OFFSET_MS && it.timestamp <= targetTimestamp + ROLLING_WINDOW_LENGTH_MS + ALLOWED_KEY_OFFSET_MS
|
||||
}.mapNotNull {
|
||||
val decrypted = key.cryptAem(it.rpi, it.aem)
|
||||
if (decrypted[0] == 0x40.toByte() || decrypted[0] == 0x50.toByte()) {
|
||||
val txPower = decrypted[1]
|
||||
it.copy(key = key, notCorrectedAttenuation = txPower - it.rssi)
|
||||
MeasuredExposure(it.timestamp, it.duration, it.rssi, txPower.toInt(), key)
|
||||
} else {
|
||||
Log.w(TAG, "Unknown AEM version ${decrypted[0]}, ignoring")
|
||||
null
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun findMeasuredExposures(rpis: List<ByteArray>, minTime: Long, maxTime: Long): List<MeasuredExposure> = readableDatabase.run {
|
||||
fun findExposures(rpis: List<ByteArray>, minTime: Long, maxTime: Long): List<PlainExposure> = readableDatabase.run {
|
||||
if (rpis.isEmpty()) return emptyList()
|
||||
val qs = rpis.map { "?" }.joinToString(",")
|
||||
queryWithFactory({ _, cursorDriver, editTable, query ->
|
||||
query.bindLong(1, minTime)
|
||||
query.bindLong(2, maxTime)
|
||||
for (i in (3..(rpis.size + 2))) {
|
||||
query.bindBlob(i, rpis[i - 3])
|
||||
rpis.forEachIndexed { index, rpi ->
|
||||
query.bindBlob(index + 3, rpi)
|
||||
}
|
||||
SQLiteCursor(cursorDriver, editTable, query)
|
||||
}, false, TABLE_ADVERTISEMENTS, arrayOf("rpi", "aem", "timestamp", "duration", "rssi"), "timestamp > ? AND timestamp < ? AND rpi IN ($qs)", null, null, null, null, null).use { cursor ->
|
||||
val list = arrayListOf<MeasuredExposure>()
|
||||
val list = arrayListOf<PlainExposure>()
|
||||
while (cursor.moveToNext()) {
|
||||
list.add(MeasuredExposure(cursor.getBlob(1), cursor.getBlob(2), cursor.getLong(3), cursor.getLong(4), cursor.getInt(5)))
|
||||
list.add(PlainExposure(cursor.getBlob(0), cursor.getBlob(1), cursor.getLong(2), cursor.getLong(3), cursor.getInt(4)))
|
||||
}
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
fun findMeasuredExposure(rpi: ByteArray, minTime: Long, maxTime: Long): MeasuredExposure? = readableDatabase.run {
|
||||
fun findExposure(rpi: ByteArray, minTime: Long, maxTime: Long): PlainExposure? = readableDatabase.run {
|
||||
queryWithFactory({ _, cursorDriver, editTable, query ->
|
||||
query.bindBlob(1, rpi)
|
||||
query.bindLong(2, minTime)
|
||||
|
@ -301,7 +295,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||
SQLiteCursor(cursorDriver, editTable, query)
|
||||
}, false, TABLE_ADVERTISEMENTS, arrayOf("aem", "timestamp", "duration", "rssi"), "rpi = ? AND timestamp > ? AND timestamp < ?", null, null, null, null, null).use { cursor ->
|
||||
if (cursor.moveToNext()) {
|
||||
MeasuredExposure(rpi, cursor.getBlob(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3))
|
||||
PlainExposure(rpi, cursor.getBlob(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -518,4 +512,3 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||
}
|
||||
if (totalBytesRead == prefix.size && String(prefix).trim() == "EK Export v1") {
|
||||
val fileKeys = storeDiagnosisKeyExport(params.token, TemporaryExposureKeyExport.ADAPTER.decode(stream))
|
||||
keys + fileKeys
|
||||
keys += fileKeys
|
||||
} else {
|
||||
Log.d(TAG, "export.bin had invalid prefix")
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||
intent.putExtra(EXTRA_TOKEN, params.token)
|
||||
intent.`package` = packageName
|
||||
Log.d(TAG, "Sending $intent")
|
||||
context.sendOrderedBroadcast(intent, PERMISSION_EXPOSURE_CALLBACK)
|
||||
context.sendOrderedBroadcast(intent, null)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Callback failed", e)
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||
}
|
||||
return@with
|
||||
}
|
||||
val exposures = database.findAllMeasuredExposures(packageName, params.token)
|
||||
val exposures = database.findAllMeasuredExposures(packageName, params.token).merge()
|
||||
val response = ExposureSummary.ExposureSummaryBuilder()
|
||||
.setDaysSinceLastExposure(exposures.map { it.daysSinceExposure }.min()?.toInt() ?: 0)
|
||||
.setMatchedKeyCount(exposures.map { it.key }.distinct().size)
|
||||
|
@ -268,7 +268,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
|||
}
|
||||
return@with
|
||||
}
|
||||
val response = database.findAllMeasuredExposures(packageName, params.token).map {
|
||||
val response = database.findAllMeasuredExposures(packageName, params.token).merge().map {
|
||||
it.toExposureInformation(configuration)
|
||||
}
|
||||
|
||||
|
|
|
@ -11,19 +11,51 @@ import com.google.android.gms.nearby.exposurenotification.RiskLevel
|
|||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
data class MeasuredExposure(val rpi: ByteArray, val aem: ByteArray, val timestamp: Long, val duration: Long, val rssi: Int, val notCorrectedAttenuation: Int = 0, val key: TemporaryExposureKey? = null) {
|
||||
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, 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 }.sortedBy { it.timestamp }) {
|
||||
if (merged == null) {
|
||||
merged = MergedExposure(key, exposure.timestamp, listOf(MergedSubExposure(exposure.attenuation, exposure.duration)))
|
||||
} else if (merged.timestamp + MergedExposure.MAXIMUM_DURATION + ROLLING_WINDOW_LENGTH_MS > exposure.timestamp) {
|
||||
merged += exposure
|
||||
}
|
||||
if (merged.durationInMinutes > 30) {
|
||||
result.add(merged)
|
||||
merged = null
|
||||
}
|
||||
}
|
||||
if (merged != null) {
|
||||
result.add(merged)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal data class MergedSubExposure(val attenuation: Int, val duration: Long)
|
||||
|
||||
data class MergedExposure internal constructor(val key: TemporaryExposureKey, val timestamp: Long, internal val subs: List<MergedSubExposure>) {
|
||||
@RiskLevel
|
||||
val transmissionRiskLevel: Int
|
||||
get() = key?.transmissionRiskLevel ?: RiskLevel.RISK_LEVEL_INVALID
|
||||
|
||||
get() = key.transmissionRiskLevel
|
||||
|
||||
val durationInMinutes
|
||||
get() = TimeUnit.MILLISECONDS.toMinutes(duration)
|
||||
get() = TimeUnit.MILLISECONDS.toMinutes(subs.map { it.duration }.sum())
|
||||
|
||||
val daysSinceExposure
|
||||
get() = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - timestamp)
|
||||
|
||||
val attenuation
|
||||
get() = notCorrectedAttenuation - currentDeviceInfo.rssiCorrection
|
||||
get() = subs.map { it.attenuation }.min()!!
|
||||
|
||||
fun getAttenuationRiskScore(configuration: ExposureConfiguration): Int {
|
||||
return when {
|
||||
|
@ -83,20 +115,26 @@ data class MeasuredExposure(val rpi: ByteArray, val aem: ByteArray, val timestam
|
|||
}
|
||||
|
||||
fun getAttenuationDurations(configuration: ExposureConfiguration): IntArray {
|
||||
return when {
|
||||
attenuation < configuration.durationAtAttenuationThresholds[0] -> intArrayOf(durationInMinutes.toInt(), 0, 0)
|
||||
attenuation < configuration.durationAtAttenuationThresholds[1] -> intArrayOf(0, durationInMinutes.toInt(), 0)
|
||||
else -> intArrayOf(0, 0, durationInMinutes.toInt())
|
||||
}
|
||||
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(timestamp)
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ class ScannerService : Service() {
|
|||
}
|
||||
|
||||
private val scanner: BluetoothLeScanner?
|
||||
get() = getDefaultAdapter().bluetoothLeScanner
|
||||
get() = getDefaultAdapter()?.bluetoothLeScanner
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.microg.gms.nearby.exposurenotification;
|
|||
|
||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
|
Loading…
Reference in New Issue