mirror of https://github.com/YTVanced/VancedMicroG
EN: Cleanup data after 14 days, improve storage efficiency, add randomness for changing RPI
This commit is contained in:
parent
c88832213c
commit
cfc1c314d4
|
@ -14,12 +14,9 @@
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
<service
|
<service android:name="org.microg.gms.nearby.exposurenotification.ScannerService" />
|
||||||
android:name="org.microg.gms.nearby.exposurenotification.ScannerService"
|
<service android:name="org.microg.gms.nearby.exposurenotification.AdvertiserService" />
|
||||||
android:exported="true" />
|
<service android:name="org.microg.gms.nearby.exposurenotification.CleanupService" />
|
||||||
<service
|
|
||||||
android:name="org.microg.gms.nearby.exposurenotification.AdvertiserService"
|
|
||||||
android:exported="true" />
|
|
||||||
|
|
||||||
<service android:name="org.microg.gms.nearby.exposurenotification.ExposureNotificationService">
|
<service android:name="org.microg.gms.nearby.exposurenotification.ExposureNotificationService">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
@ -7,8 +7,11 @@ package org.microg.gms.nearby.exposurenotification
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.bluetooth.le.*
|
import android.bluetooth.le.AdvertiseCallback
|
||||||
|
import android.bluetooth.le.AdvertiseData
|
||||||
|
import android.bluetooth.le.AdvertiseSettings
|
||||||
import android.bluetooth.le.AdvertiseSettings.*
|
import android.bluetooth.le.AdvertiseSettings.*
|
||||||
|
import android.bluetooth.le.BluetoothLeAdvertiser
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -17,6 +20,7 @@ import android.util.Log
|
||||||
import androidx.lifecycle.LifecycleService
|
import androidx.lifecycle.LifecycleService
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import org.microg.gms.common.ForegroundServiceContext
|
||||||
import java.io.FileDescriptor
|
import java.io.FileDescriptor
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
@ -24,6 +28,7 @@ import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
class AdvertiserService : LifecycleService() {
|
class AdvertiserService : LifecycleService() {
|
||||||
|
@ -69,6 +74,7 @@ class AdvertiserService : LifecycleService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
if (ExposurePreferences(this).advertiserEnabled) {
|
if (ExposurePreferences(this).advertiserEnabled) {
|
||||||
loopAdvertising()
|
loopAdvertising()
|
||||||
|
@ -109,7 +115,7 @@ class AdvertiserService : LifecycleService() {
|
||||||
else -> return@launchWhenStarted
|
else -> return@launchWhenStarted
|
||||||
}
|
}
|
||||||
val payload = database.generateCurrentPayload(aem)
|
val payload = database.generateCurrentPayload(aem)
|
||||||
var nextSend = nextKeyMillis.coerceAtMost(180000)
|
val nextSend = (nextKeyMillis + Random.nextInt(-ADVERTISER_OFFSET, ADVERTISER_OFFSET)).coerceIn(0, 180000)
|
||||||
startAdvertising(payload, nextSend.toInt())
|
startAdvertising(payload, nextSend.toInt())
|
||||||
if (callback != null) delay(nextSend)
|
if (callback != null) delay(nextSend)
|
||||||
} while (callback != null)
|
} while (callback != null)
|
||||||
|
@ -182,4 +188,10 @@ class AdvertiserService : LifecycleService() {
|
||||||
callback?.let { advertiser?.stopAdvertising(it) }
|
callback?.let { advertiser?.stopAdvertising(it) }
|
||||||
callback = null
|
callback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun isNeeded(context: Context): Boolean {
|
||||||
|
return ExposurePreferences(context).scannerEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.gms.nearby.exposurenotification
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.lifecycle.LifecycleService
|
||||||
|
import org.microg.gms.common.ForegroundServiceContext
|
||||||
|
|
||||||
|
class CleanupService : LifecycleService() {
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||||
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
if (isNeeded(this)) {
|
||||||
|
ExposureDatabase.with(this@CleanupService) {
|
||||||
|
it.dailyCleanup()
|
||||||
|
}
|
||||||
|
ExposurePreferences(this).lastCleanup = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
stopSelf()
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun isNeeded(context: Context): Boolean {
|
||||||
|
return ExposurePreferences(context).let {
|
||||||
|
it.scannerEnabled && it.lastCleanup < System.currentTimeMillis() - CLEANUP_INTERVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,9 @@ const val PERMISSION_EXPOSURE_CALLBACK = "com.google.android.gms.nearby.exposure
|
||||||
|
|
||||||
const val TX_POWER_LOW = -15
|
const val TX_POWER_LOW = -15
|
||||||
|
|
||||||
|
const val ADVERTISER_OFFSET = 60 * 1000
|
||||||
|
const val CLEANUP_INTERVAL = 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
const val VERSION_1_0: Byte = 0x40
|
const val VERSION_1_0: Byte = 0x40
|
||||||
const val VERSION_1_1: Byte = 0x50
|
const val VERSION_1_1: Byte = 0x50
|
||||||
|
|
||||||
|
|
|
@ -5,28 +5,38 @@
|
||||||
|
|
||||||
package org.microg.gms.nearby.exposurenotification
|
package org.microg.gms.nearby.exposurenotification
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteCursor
|
import android.database.sqlite.SQLiteCursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
import android.database.sqlite.SQLiteStatement
|
||||||
|
import android.os.Build
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
|
||||||
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||||
|
import org.microg.gms.common.PackageUtils
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
import java.util.concurrent.ThreadPoolExecutor
|
import java.util.concurrent.ThreadPoolExecutor
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
@TargetApi(21)
|
||||||
|
class ExposureDatabase private constructor(private val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||||
private var refCount = 0
|
private var refCount = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
setWriteAheadLoggingEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(db: SQLiteDatabase) {
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
onUpgrade(db, 0, DB_VERSION)
|
onUpgrade(db, 0, DB_VERSION)
|
||||||
}
|
}
|
||||||
|
@ -39,19 +49,30 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_APP_LOG(package TEXT NOT NULL, timestamp INTEGER NOT NULL, method TEXT NOT NULL, args TEXT);")
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_APP_LOG(package TEXT NOT NULL, timestamp INTEGER NOT NULL, method TEXT NOT NULL, args TEXT);")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_APP_LOG}_package_timestamp ON $TABLE_APP_LOG(package, timestamp);")
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_APP_LOG}_package_timestamp ON $TABLE_APP_LOG(package, timestamp);")
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK(keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL);")
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK(keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL);")
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK(keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(keyData, rollingStartNumber, rollingPeriod));")
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_DIAGNOSIS(package TEXT NOT NULL, token TEXT NOT NULL, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, transmissionRiskLevel INTEGER NOT NULL, PRIMARY KEY(package, token, keyData));")
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_CONFIGURATIONS(package TEXT NOT NULL, token TEXT NOT NULL, configuration BLOB, PRIMARY KEY(package, token))")
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_CONFIGURATIONS(package TEXT NOT NULL, token TEXT NOT NULL, configuration BLOB, PRIMARY KEY(package, token))")
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 2) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS $TABLE_TEK_CHECK;")
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS $TABLE_DIAGNOSIS;")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK(tcid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER, UNIQUE(keyData, rollingStartNumber, rollingPeriod));")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_DIAGNOSIS(package TEXT NOT NULL, token TEXT NOT NULL, tcid INTEGER REFERENCES $TABLE_TEK_CHECK(tcid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL);")
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_DIAGNOSIS}_package_token ON $TABLE_DIAGNOSIS(package, token);")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray): Int =
|
||||||
|
compileStatement("DELETE FROM $table WHERE $whereClause").use {
|
||||||
|
args.forEachIndexed { idx, l -> it.bindLong(idx + 1, l) }
|
||||||
|
it.executeUpdateDelete()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dailyCleanup() = writableDatabase.run {
|
fun dailyCleanup() = writableDatabase.run {
|
||||||
val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong())
|
val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong())
|
||||||
delete(TABLE_ADVERTISEMENTS, "timestamp < ?", arrayOf(rollingStartTime.toString()))
|
val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime))
|
||||||
delete(TABLE_APP_LOG, "timestamp < ?", arrayOf(rollingStartTime.toString()))
|
val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime))
|
||||||
delete(TABLE_TEK, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString()))
|
val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS))
|
||||||
delete(TABLE_TEK_CHECK, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString()))
|
val checkedTemporaryExposureKeys = delete(TABLE_TEK_CHECK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS))
|
||||||
delete(TABLE_DIAGNOSIS, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString()))
|
Log.d(TAG, "Deleted on daily cleanup: $advertisements adv, $appLogEntries applogs, $temporaryExposureKeys teks, $checkedTemporaryExposureKeys cteks")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun noteAdvertisement(rpi: ByteArray, aem: ByteArray, rssi: Int, timestamp: Long = Date().time) = writableDatabase.run {
|
fun noteAdvertisement(rpi: ByteArray, aem: ByteArray, rssi: Int, timestamp: Long = Date().time) = writableDatabase.run {
|
||||||
|
@ -78,7 +99,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(
|
||||||
|
|
||||||
fun deleteAllCollectedAdvertisements() = writableDatabase.run {
|
fun deleteAllCollectedAdvertisements() = writableDatabase.run {
|
||||||
delete(TABLE_ADVERTISEMENTS, null, null)
|
delete(TABLE_ADVERTISEMENTS, null, null)
|
||||||
update(TABLE_DIAGNOSIS, ContentValues().apply {
|
update(TABLE_TEK_CHECK, ContentValues().apply {
|
||||||
put("matched", 0)
|
put("matched", 0)
|
||||||
}, null, null)
|
}, null, null)
|
||||||
}
|
}
|
||||||
|
@ -102,37 +123,48 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey) = writableDatabase.run {
|
fun getTekCheckId(key: TemporaryExposureKey, mayInsert: Boolean = false): Long? = (if (mayInsert) writableDatabase else readableDatabase).run {
|
||||||
insert(TABLE_DIAGNOSIS, "NULL", ContentValues().apply {
|
if (mayInsert) {
|
||||||
put("package", packageName)
|
insertWithOnConflict(TABLE_TEK_CHECK, "NULL", ContentValues().apply {
|
||||||
put("token", token)
|
|
||||||
put("keyData", key.keyData)
|
put("keyData", key.keyData)
|
||||||
put("rollingStartNumber", key.rollingStartIntervalNumber)
|
put("rollingStartNumber", key.rollingStartIntervalNumber)
|
||||||
put("rollingPeriod", key.rollingPeriod)
|
put("rollingPeriod", key.rollingPeriod)
|
||||||
|
}, CONFLICT_IGNORE)
|
||||||
|
}
|
||||||
|
compileStatement("SELECT tcid FROM $TABLE_TEK_CHECK WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?").use {
|
||||||
|
it.bindBlob(1, key.keyData)
|
||||||
|
it.bindLong(2, key.rollingStartIntervalNumber.toLong())
|
||||||
|
it.bindLong(3, key.rollingPeriod.toLong())
|
||||||
|
it.simpleQueryForLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun storeDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey) = writableDatabase.run {
|
||||||
|
val tcid = getTekCheckId(key, true)
|
||||||
|
insert(TABLE_DIAGNOSIS, "NULL", ContentValues().apply {
|
||||||
|
put("package", packageName)
|
||||||
|
put("token", token)
|
||||||
|
put("tcid", tcid)
|
||||||
put("transmissionRiskLevel", key.transmissionRiskLevel)
|
put("transmissionRiskLevel", key.transmissionRiskLevel)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey) = writableDatabase.run {
|
fun updateDiagnosisKey(packageName: String, token: String, key: TemporaryExposureKey) = writableDatabase.run {
|
||||||
compileStatement("UPDATE $TABLE_DIAGNOSIS SET rollingStartNumber = ?, rollingPeriod = ?, transmissionRiskLevel = ? WHERE package = ? AND token = ? AND keyData = ?;").use {
|
val tcid = getTekCheckId(key) ?: return 0
|
||||||
it.bindLong(1, key.rollingStartIntervalNumber.toLong())
|
compileStatement("UPDATE $TABLE_DIAGNOSIS SET transmissionRiskLevel = ? WHERE package = ? AND token = ? AND tcid = ?;").use {
|
||||||
it.bindLong(2, key.rollingPeriod.toLong())
|
it.bindLong(1, key.transmissionRiskLevel.toLong())
|
||||||
it.bindLong(3, key.transmissionRiskLevel.toLong())
|
it.bindString(2, packageName)
|
||||||
it.bindString(4, packageName)
|
it.bindString(3, token)
|
||||||
it.bindString(5, token)
|
it.bindLong(4, tcid)
|
||||||
it.bindBlob(6, key.keyData)
|
|
||||||
it.executeUpdateDelete()
|
it.executeUpdateDelete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun listDiagnosisKeysPendingSearch(packageName: String, token: String) = readableDatabase.run {
|
fun listDiagnosisKeysPendingSearch(packageName: String, token: String) = readableDatabase.run {
|
||||||
rawQuery("""
|
rawQuery("""
|
||||||
SELECT $TABLE_DIAGNOSIS.keyData, $TABLE_DIAGNOSIS.rollingStartNumber, $TABLE_DIAGNOSIS.rollingPeriod
|
SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod
|
||||||
FROM $TABLE_DIAGNOSIS
|
FROM $TABLE_DIAGNOSIS
|
||||||
LEFT JOIN $TABLE_TEK_CHECK ON
|
LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid
|
||||||
$TABLE_DIAGNOSIS.keyData = $TABLE_TEK_CHECK.keyData AND
|
|
||||||
$TABLE_DIAGNOSIS.rollingStartNumber = $TABLE_TEK_CHECK.rollingStartNumber AND
|
|
||||||
$TABLE_DIAGNOSIS.rollingPeriod = $TABLE_TEK_CHECK.rollingPeriod
|
|
||||||
WHERE
|
WHERE
|
||||||
$TABLE_DIAGNOSIS.package = ? AND
|
$TABLE_DIAGNOSIS.package = ? AND
|
||||||
$TABLE_DIAGNOSIS.token = ? AND
|
$TABLE_DIAGNOSIS.token = ? AND
|
||||||
|
@ -151,22 +183,20 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean) = writableDatabase.run {
|
fun applyDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean) = writableDatabase.run {
|
||||||
insert(TABLE_TEK_CHECK, "NULL", ContentValues().apply {
|
compileStatement("UPDATE $TABLE_TEK_CHECK SET matched = ? WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?;").use {
|
||||||
put("keyData", key.keyData)
|
it.bindLong(1, if (matched) 1 else 0)
|
||||||
put("rollingStartNumber", key.rollingStartIntervalNumber)
|
it.bindBlob(2, key.keyData)
|
||||||
put("rollingPeriod", key.rollingPeriod)
|
it.bindLong(3, key.rollingStartIntervalNumber.toLong())
|
||||||
put("matched", if (matched) 1 else 0)
|
it.bindLong(4, key.rollingPeriod.toLong())
|
||||||
})
|
it.executeUpdateDelete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun listMatchedDiagnosisKeys(packageName: String, token: String) = readableDatabase.run {
|
fun listMatchedDiagnosisKeys(packageName: String, token: String) = readableDatabase.run {
|
||||||
rawQuery("""
|
rawQuery("""
|
||||||
SELECT $TABLE_DIAGNOSIS.keyData, $TABLE_DIAGNOSIS.rollingStartNumber, $TABLE_DIAGNOSIS.rollingPeriod, $TABLE_DIAGNOSIS.transmissionRiskLevel
|
SELECT $TABLE_TEK_CHECK.keyData, $TABLE_TEK_CHECK.rollingStartNumber, $TABLE_TEK_CHECK.rollingPeriod, $TABLE_DIAGNOSIS.transmissionRiskLevel
|
||||||
FROM $TABLE_DIAGNOSIS
|
FROM $TABLE_DIAGNOSIS
|
||||||
LEFT JOIN $TABLE_TEK_CHECK ON
|
LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid
|
||||||
$TABLE_DIAGNOSIS.keyData = $TABLE_TEK_CHECK.keyData AND
|
|
||||||
$TABLE_DIAGNOSIS.rollingStartNumber = $TABLE_TEK_CHECK.rollingStartNumber AND
|
|
||||||
$TABLE_DIAGNOSIS.rollingPeriod = $TABLE_TEK_CHECK.rollingPeriod
|
|
||||||
WHERE
|
WHERE
|
||||||
$TABLE_DIAGNOSIS.package = ? AND
|
$TABLE_DIAGNOSIS.package = ? AND
|
||||||
$TABLE_DIAGNOSIS.token = ? AND
|
$TABLE_DIAGNOSIS.token = ? AND
|
||||||
|
@ -208,7 +238,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(
|
||||||
}
|
}
|
||||||
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
val time = (System.currentTimeMillis() - start).toDouble() / 1000.0
|
||||||
executor.shutdown()
|
executor.shutdown()
|
||||||
Log.d(TAG, "Processed ${keys.size} keys in ${time}s -> ${(keys.size.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
Log.d(TAG, "Processed ${keys.size} new keys in ${time}s -> ${(keys.size.toDouble() / time * 1000).roundToInt().toDouble() / 1000.0} keys/s")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findAllMeasuredExposures(packageName: String, token: String): List<MeasuredExposure> {
|
fun findAllMeasuredExposures(packageName: String, token: String): List<MeasuredExposure> {
|
||||||
|
@ -226,7 +256,6 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(
|
||||||
val pos = i * 16
|
val pos = i * 16
|
||||||
allRpis.sliceArray(pos until (pos + 16))
|
allRpis.sliceArray(pos until (pos + 16))
|
||||||
}
|
}
|
||||||
val start = System.currentTimeMillis()
|
|
||||||
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)
|
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 {
|
measures.filter {
|
||||||
val index = rpis.indexOf(it.rpi)
|
val index = rpis.indexOf(it.rpi)
|
||||||
|
@ -437,9 +466,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(
|
||||||
if (this != instance) {
|
if (this != instance) {
|
||||||
throw IllegalStateException("Tried to open writable database from secondary instance")
|
throw IllegalStateException("Tried to open writable database from secondary instance")
|
||||||
}
|
}
|
||||||
val db = super.getWritableDatabase()
|
return super.getWritableDatabase()
|
||||||
db.enableWriteAheadLogging()
|
|
||||||
return db
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
@ -465,7 +492,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DB_NAME = "exposure.db"
|
private const val DB_NAME = "exposure.db"
|
||||||
private const val DB_VERSION = 1
|
private const val DB_VERSION = 2
|
||||||
private const val TABLE_ADVERTISEMENTS = "advertisements"
|
private const val TABLE_ADVERTISEMENTS = "advertisements"
|
||||||
private const val TABLE_APP_LOG = "app_log"
|
private const val TABLE_APP_LOG = "app_log"
|
||||||
private const val TABLE_TEK = "tek"
|
private const val TABLE_TEK = "tek"
|
||||||
|
|
|
@ -19,8 +19,7 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
|
||||||
import com.google.android.gms.nearby.exposurenotification.internal.*
|
import com.google.android.gms.nearby.exposurenotification.internal.*
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.microg.gms.nearby.exposurenotification.Constants.ACTION_EXPOSURE_NOT_FOUND
|
import org.microg.gms.nearby.exposurenotification.Constants.*
|
||||||
import org.microg.gms.nearby.exposurenotification.Constants.ACTION_EXPOSURE_STATE_UPDATED
|
|
||||||
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyExport
|
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyExport
|
||||||
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto
|
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -73,6 +72,10 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stop(params: StopParams) {
|
override fun stop(params: StopParams) {
|
||||||
|
if (!ExposurePreferences(context).scannerEnabled) {
|
||||||
|
params.callback.onResult(Status.SUCCESS)
|
||||||
|
return
|
||||||
|
}
|
||||||
confirm(CONFIRM_ACTION_STOP) { resultCode, _ ->
|
confirm(CONFIRM_ACTION_STOP) { resultCode, _ ->
|
||||||
if (resultCode == SUCCESS) {
|
if (resultCode == SUCCESS) {
|
||||||
ExposurePreferences(context).scannerEnabled = false
|
ExposurePreferences(context).scannerEnabled = false
|
||||||
|
@ -189,6 +192,8 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||||
put("request_keys_count", keys)
|
put("request_keys_count", keys)
|
||||||
}.toString())
|
}.toString())
|
||||||
|
|
||||||
|
database.finishMatching(packageName, params.token)
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
try {
|
||||||
params.callback.onResult(Status.SUCCESS)
|
params.callback.onResult(Status.SUCCESS)
|
||||||
|
@ -197,11 +202,11 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
database.finishMatching(packageName, params.token)
|
|
||||||
val match = database.findAllMeasuredExposures(packageName, params.token).isNotEmpty()
|
val match = database.findAllMeasuredExposures(packageName, params.token).isNotEmpty()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val intent = Intent(if (match) ACTION_EXPOSURE_STATE_UPDATED else ACTION_EXPOSURE_NOT_FOUND)
|
val intent = Intent(if (match) ACTION_EXPOSURE_STATE_UPDATED else ACTION_EXPOSURE_NOT_FOUND)
|
||||||
|
intent.putExtra(EXTRA_TOKEN, params.token)
|
||||||
intent.`package` = packageName
|
intent.`package` = packageName
|
||||||
Log.d(TAG, "Sending $intent")
|
Log.d(TAG, "Sending $intent")
|
||||||
context.sendOrderedBroadcast(intent, PERMISSION_EXPOSURE_CALLBACK)
|
context.sendOrderedBroadcast(intent, PERMISSION_EXPOSURE_CALLBACK)
|
||||||
|
|
|
@ -29,7 +29,12 @@ class ExposurePreferences(private val context: Context) {
|
||||||
val advertiserEnabled
|
val advertiserEnabled
|
||||||
get() = scannerEnabled
|
get() = scannerEnabled
|
||||||
|
|
||||||
|
var lastCleanup
|
||||||
|
get() = preferences.getLong(PREF_LAST_CLEANUP, 0)
|
||||||
|
set(value) = preferences.edit().putLong(PREF_LAST_CLEANUP, value).apply()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PREF_SCANNER_ENABLED = "exposure_scanner_enabled"
|
private const val PREF_SCANNER_ENABLED = "exposure_scanner_enabled"
|
||||||
|
private const val PREF_LAST_CLEANUP = "exposure_last_cleanup"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import org.microg.gms.common.ForegroundServiceContext
|
||||||
import java.io.FileDescriptor
|
import java.io.FileDescriptor
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -26,6 +27,7 @@ class ScannerService : Service() {
|
||||||
private var started = false
|
private var started = false
|
||||||
private var startTime = 0L
|
private var startTime = 0L
|
||||||
private var seenAdvertisements = 0L
|
private var seenAdvertisements = 0L
|
||||||
|
private var lastAdvertisement = 0L
|
||||||
private lateinit var database: ExposureDatabase
|
private lateinit var database: ExposureDatabase
|
||||||
private val callback = object : ScanCallback() {
|
private val callback = object : ScanCallback() {
|
||||||
override fun onScanResult(callbackType: Int, result: ScanResult?) {
|
override fun onScanResult(callbackType: Int, result: ScanResult?) {
|
||||||
|
@ -59,6 +61,7 @@ class ScannerService : Service() {
|
||||||
get() = getDefaultAdapter().bluetoothLeScanner
|
get() = getDefaultAdapter().bluetoothLeScanner
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
startScanIfNeeded()
|
startScanIfNeeded()
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
|
@ -69,6 +72,7 @@ class ScannerService : Service() {
|
||||||
if (data.size < 16) return // Ignore invalid advertisements
|
if (data.size < 16) return // Ignore invalid advertisements
|
||||||
database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi)
|
database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi)
|
||||||
seenAdvertisements++
|
seenAdvertisements++
|
||||||
|
lastAdvertisement = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startScanIfNeeded() {
|
fun startScanIfNeeded() {
|
||||||
|
@ -128,6 +132,13 @@ class ScannerService : Service() {
|
||||||
if (started) {
|
if (started) {
|
||||||
writer?.println("Since ${Date(startTime)}")
|
writer?.println("Since ${Date(startTime)}")
|
||||||
writer?.println("Seen advertisements: $seenAdvertisements")
|
writer?.println("Seen advertisements: $seenAdvertisements")
|
||||||
|
writer?.println("Last advertisement: ${Date(lastAdvertisement)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun isNeeded(context: Context): Boolean {
|
||||||
|
return ExposurePreferences(context).scannerEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,15 @@ class ServiceTrigger : BroadcastReceiver() {
|
||||||
@SuppressLint("UnsafeProtectedBroadcastReceiver")
|
@SuppressLint("UnsafeProtectedBroadcastReceiver")
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
Log.d(TAG, "ServiceTrigger: $intent")
|
Log.d(TAG, "ServiceTrigger: $intent")
|
||||||
if (ExposurePreferences(context).scannerEnabled) {
|
val serviceContext = ForegroundServiceContext(context)
|
||||||
ForegroundServiceContext(context).startService(Intent(context, ScannerService::class.java))
|
if (ScannerService.isNeeded(context)) {
|
||||||
|
serviceContext.startService(Intent(context, ScannerService::class.java))
|
||||||
}
|
}
|
||||||
if (ExposurePreferences(context).advertiserEnabled) {
|
if (AdvertiserService.isNeeded(context)) {
|
||||||
ForegroundServiceContext(context).startService(Intent(context, AdvertiserService::class.java))
|
serviceContext.startService(Intent(context, AdvertiserService::class.java))
|
||||||
|
}
|
||||||
|
if (CleanupService.isNeeded(context)) {
|
||||||
|
serviceContext.startService(Intent(context, CleanupService::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue