From cfc1c314d4eac9a4f763678e4734f658d527634e Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 24 Aug 2020 10:12:49 +0200 Subject: [PATCH] EN: Cleanup data after 14 days, improve storage efficiency, add randomness for changing RPI --- .../src/main/AndroidManifest.xml | 9 +- .../exposurenotification/AdvertiserService.kt | 16 ++- .../exposurenotification/CleanupService.kt | 34 ++++++ .../nearby/exposurenotification/Constants.kt | 3 + .../exposurenotification/ExposureDatabase.kt | 111 +++++++++++------- .../ExposureNotificationServiceImpl.kt | 11 +- .../ExposurePreferences.kt | 5 + .../exposurenotification/ScannerService.kt | 11 ++ .../exposurenotification/ServiceTrigger.kt | 12 +- 9 files changed, 155 insertions(+), 57 deletions(-) create mode 100644 play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt diff --git a/play-services-nearby-core/src/main/AndroidManifest.xml b/play-services-nearby-core/src/main/AndroidManifest.xml index c8e03e70..83a8bda1 100644 --- a/play-services-nearby-core/src/main/AndroidManifest.xml +++ b/play-services-nearby-core/src/main/AndroidManifest.xml @@ -14,12 +14,9 @@ - - + + + diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt index e3e166c1..417aaf8e 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt @@ -7,8 +7,11 @@ package org.microg.gms.nearby.exposurenotification import android.annotation.TargetApi 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.BluetoothLeAdvertiser import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -17,6 +20,7 @@ import android.util.Log import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.delay +import org.microg.gms.common.ForegroundServiceContext import java.io.FileDescriptor import java.io.PrintWriter import java.nio.ByteBuffer @@ -24,6 +28,7 @@ import java.util.* import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine +import kotlin.random.Random @TargetApi(21) class AdvertiserService : LifecycleService() { @@ -69,6 +74,7 @@ class AdvertiserService : LifecycleService() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + ForegroundServiceContext.completeForegroundService(this, intent, TAG) super.onStartCommand(intent, flags, startId) if (ExposurePreferences(this).advertiserEnabled) { loopAdvertising() @@ -109,7 +115,7 @@ class AdvertiserService : LifecycleService() { else -> return@launchWhenStarted } 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()) if (callback != null) delay(nextSend) } while (callback != null) @@ -182,4 +188,10 @@ class AdvertiserService : LifecycleService() { callback?.let { advertiser?.stopAdvertising(it) } callback = null } + + companion object { + fun isNeeded(context: Context): Boolean { + return ExposurePreferences(context).scannerEnabled + } + } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt new file mode 100644 index 00000000..f7908f85 --- /dev/null +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt @@ -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 + } + } + } +} diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt index d41b53bb..891d80c1 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt @@ -30,6 +30,9 @@ const val PERMISSION_EXPOSURE_CALLBACK = "com.google.android.gms.nearby.exposure 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_1: Byte = 0x50 diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt index 077be955..4f435efe 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt @@ -5,28 +5,38 @@ package org.microg.gms.nearby.exposurenotification +import android.annotation.TargetApi import android.content.ContentValues import android.content.Context 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 import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger 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 + init { + setWriteAheadLoggingEnabled(true) + } + override fun onCreate(db: SQLiteDatabase) { 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 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_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))") } + 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 { val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong()) - delete(TABLE_ADVERTISEMENTS, "timestamp < ?", arrayOf(rollingStartTime.toString())) - delete(TABLE_APP_LOG, "timestamp < ?", arrayOf(rollingStartTime.toString())) - delete(TABLE_TEK, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString())) - delete(TABLE_TEK_CHECK, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString())) - delete(TABLE_DIAGNOSIS, "rollingStartNumber + rollingPeriod < ?", arrayOf((rollingStartTime / ROLLING_WINDOW_LENGTH_MS).toString())) + val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime)) + val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime)) + val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS)) + val checkedTemporaryExposureKeys = delete(TABLE_TEK_CHECK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS)) + 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 { @@ -78,7 +99,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( fun deleteAllCollectedAdvertisements() = writableDatabase.run { delete(TABLE_ADVERTISEMENTS, null, null) - update(TABLE_DIAGNOSIS, ContentValues().apply { + update(TABLE_TEK_CHECK, ContentValues().apply { put("matched", 0) }, null, null) } @@ -102,37 +123,48 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( key } + fun getTekCheckId(key: TemporaryExposureKey, mayInsert: Boolean = false): Long? = (if (mayInsert) writableDatabase else readableDatabase).run { + if (mayInsert) { + insertWithOnConflict(TABLE_TEK_CHECK, "NULL", ContentValues().apply { + put("keyData", key.keyData) + put("rollingStartNumber", key.rollingStartIntervalNumber) + 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("keyData", key.keyData) - put("rollingStartNumber", key.rollingStartIntervalNumber) - put("rollingPeriod", key.rollingPeriod) + put("tcid", tcid) put("transmissionRiskLevel", key.transmissionRiskLevel) }) } 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 { - it.bindLong(1, key.rollingStartIntervalNumber.toLong()) - it.bindLong(2, key.rollingPeriod.toLong()) - it.bindLong(3, key.transmissionRiskLevel.toLong()) - it.bindString(4, packageName) - it.bindString(5, token) - it.bindBlob(6, key.keyData) + val tcid = getTekCheckId(key) ?: return 0 + compileStatement("UPDATE $TABLE_DIAGNOSIS SET transmissionRiskLevel = ? WHERE package = ? AND token = ? AND tcid = ?;").use { + it.bindLong(1, key.transmissionRiskLevel.toLong()) + it.bindString(2, packageName) + it.bindString(3, token) + it.bindLong(4, tcid) it.executeUpdateDelete() } } fun listDiagnosisKeysPendingSearch(packageName: String, token: String) = readableDatabase.run { 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 - LEFT JOIN $TABLE_TEK_CHECK ON - $TABLE_DIAGNOSIS.keyData = $TABLE_TEK_CHECK.keyData AND - $TABLE_DIAGNOSIS.rollingStartNumber = $TABLE_TEK_CHECK.rollingStartNumber AND - $TABLE_DIAGNOSIS.rollingPeriod = $TABLE_TEK_CHECK.rollingPeriod + LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid WHERE $TABLE_DIAGNOSIS.package = ? AND $TABLE_DIAGNOSIS.token = ? AND @@ -151,22 +183,20 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( } fun applyDiagnosisKeySearchResult(key: TemporaryExposureKey, matched: Boolean) = writableDatabase.run { - insert(TABLE_TEK_CHECK, "NULL", ContentValues().apply { - put("keyData", key.keyData) - put("rollingStartNumber", key.rollingStartIntervalNumber) - put("rollingPeriod", key.rollingPeriod) - put("matched", if (matched) 1 else 0) - }) + compileStatement("UPDATE $TABLE_TEK_CHECK SET matched = ? WHERE keyData = ? AND rollingStartNumber = ? AND rollingPeriod = ?;").use { + it.bindLong(1, if (matched) 1 else 0) + it.bindBlob(2, key.keyData) + it.bindLong(3, key.rollingStartIntervalNumber.toLong()) + it.bindLong(4, key.rollingPeriod.toLong()) + it.executeUpdateDelete() + } } fun listMatchedDiagnosisKeys(packageName: String, token: String) = readableDatabase.run { 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 - LEFT JOIN $TABLE_TEK_CHECK ON - $TABLE_DIAGNOSIS.keyData = $TABLE_TEK_CHECK.keyData AND - $TABLE_DIAGNOSIS.rollingStartNumber = $TABLE_TEK_CHECK.rollingStartNumber AND - $TABLE_DIAGNOSIS.rollingPeriod = $TABLE_TEK_CHECK.rollingPeriod + LEFT JOIN $TABLE_TEK_CHECK ON $TABLE_DIAGNOSIS.tcid = $TABLE_TEK_CHECK.tcid WHERE $TABLE_DIAGNOSIS.package = ? AND $TABLE_DIAGNOSIS.token = ? AND @@ -208,7 +238,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( } val time = (System.currentTimeMillis() - start).toDouble() / 1000.0 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 { @@ -226,7 +256,6 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( val pos = i * 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) measures.filter { val index = rpis.indexOf(it.rpi) @@ -437,9 +466,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( if (this != instance) { throw IllegalStateException("Tried to open writable database from secondary instance") } - val db = super.getWritableDatabase() - db.enableWriteAheadLogging() - return db + return super.getWritableDatabase() } override fun close() { @@ -465,7 +492,7 @@ class ExposureDatabase private constructor(context: Context) : SQLiteOpenHelper( companion object { 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_APP_LOG = "app_log" private const val TABLE_TEK = "tek" diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 444384c4..efbb7c74 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -19,8 +19,7 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.google.android.gms.nearby.exposurenotification.internal.* import org.json.JSONArray import org.json.JSONObject -import org.microg.gms.nearby.exposurenotification.Constants.ACTION_EXPOSURE_NOT_FOUND -import org.microg.gms.nearby.exposurenotification.Constants.ACTION_EXPOSURE_STATE_UPDATED +import org.microg.gms.nearby.exposurenotification.Constants.* import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyExport import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto import java.util.* @@ -73,6 +72,10 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } override fun stop(params: StopParams) { + if (!ExposurePreferences(context).scannerEnabled) { + params.callback.onResult(Status.SUCCESS) + return + } confirm(CONFIRM_ACTION_STOP) { resultCode, _ -> if (resultCode == SUCCESS) { ExposurePreferences(context).scannerEnabled = false @@ -189,6 +192,8 @@ class ExposureNotificationServiceImpl(private val context: Context, private val put("request_keys_count", keys) }.toString()) + database.finishMatching(packageName, params.token) + Handler(Looper.getMainLooper()).post { try { 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() try { val intent = Intent(if (match) ACTION_EXPOSURE_STATE_UPDATED else ACTION_EXPOSURE_NOT_FOUND) + intent.putExtra(EXTRA_TOKEN, params.token) intent.`package` = packageName Log.d(TAG, "Sending $intent") context.sendOrderedBroadcast(intent, PERMISSION_EXPOSURE_CALLBACK) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt index b10908af..1945d519 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposurePreferences.kt @@ -29,7 +29,12 @@ class ExposurePreferences(private val context: Context) { val advertiserEnabled get() = scannerEnabled + var lastCleanup + get() = preferences.getLong(PREF_LAST_CLEANUP, 0) + set(value) = preferences.edit().putLong(PREF_LAST_CLEANUP, value).apply() + companion object { private const val PREF_SCANNER_ENABLED = "exposure_scanner_enabled" + private const val PREF_LAST_CLEANUP = "exposure_last_cleanup" } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index a2f020d7..f180e1d6 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -17,6 +17,7 @@ import android.content.IntentFilter import android.os.Build import android.os.IBinder import android.util.Log +import org.microg.gms.common.ForegroundServiceContext import java.io.FileDescriptor import java.io.PrintWriter import java.util.* @@ -26,6 +27,7 @@ class ScannerService : Service() { private var started = false private var startTime = 0L private var seenAdvertisements = 0L + private var lastAdvertisement = 0L private lateinit var database: ExposureDatabase private val callback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult?) { @@ -59,6 +61,7 @@ class ScannerService : Service() { get() = getDefaultAdapter().bluetoothLeScanner override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + ForegroundServiceContext.completeForegroundService(this, intent, TAG) super.onStartCommand(intent, flags, startId) startScanIfNeeded() return START_STICKY @@ -69,6 +72,7 @@ class ScannerService : Service() { if (data.size < 16) return // Ignore invalid advertisements database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi) seenAdvertisements++ + lastAdvertisement = System.currentTimeMillis() } fun startScanIfNeeded() { @@ -128,6 +132,13 @@ class ScannerService : Service() { if (started) { writer?.println("Since ${Date(startTime)}") writer?.println("Seen advertisements: $seenAdvertisements") + writer?.println("Last advertisement: ${Date(lastAdvertisement)}") + } + } + + companion object { + fun isNeeded(context: Context): Boolean { + return ExposurePreferences(context).scannerEnabled } } } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt index b6c5cfc3..a375c9c9 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ServiceTrigger.kt @@ -16,11 +16,15 @@ class ServiceTrigger : BroadcastReceiver() { @SuppressLint("UnsafeProtectedBroadcastReceiver") override fun onReceive(context: Context, intent: Intent?) { Log.d(TAG, "ServiceTrigger: $intent") - if (ExposurePreferences(context).scannerEnabled) { - ForegroundServiceContext(context).startService(Intent(context, ScannerService::class.java)) + val serviceContext = ForegroundServiceContext(context) + if (ScannerService.isNeeded(context)) { + serviceContext.startService(Intent(context, ScannerService::class.java)) } - if (ExposurePreferences(context).advertiserEnabled) { - ForegroundServiceContext(context).startService(Intent(context, AdvertiserService::class.java)) + if (AdvertiserService.isNeeded(context)) { + serviceContext.startService(Intent(context, AdvertiserService::class.java)) + } + if (CleanupService.isNeeded(context)) { + serviceContext.startService(Intent(context, CleanupService::class.java)) } } }