0
0
Fork 0
mirror of https://github.com/YTVanced/VancedMicroG synced 2024-11-30 23:23:01 +00:00

Update EN API

This commit is contained in:
Marvin W 2020-08-18 23:54:14 +02:00
parent e1bb395ff8
commit aea55a5c90
No known key found for this signature in database
GPG key ID: 072E9235DB996F2A
6 changed files with 169 additions and 42 deletions

View file

@ -45,7 +45,7 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() {
val (totalRpiCount, rpiHistogram) = withContext(Dispatchers.IO) { val (totalRpiCount, rpiHistogram) = withContext(Dispatchers.IO) {
ExposureDatabase.with(requireContext()) { database -> ExposureDatabase.with(requireContext()) { database ->
val map = linkedMapOf<String, Float>() val map = linkedMapOf<String, Float>()
val lowestDate = Math.round((Date().time / 24 / 60 / 60 / 1000 - 13).toDouble()) * 24 * 60 * 60 * 1000 val lowestDate = Math.round((System.currentTimeMillis() / 24 / 60 / 60 / 1000 - 13).toDouble()) * 24 * 60 * 60 * 1000
for (i in 0..13) { for (i in 0..13) {
val date = Calendar.getInstance().apply { this.time = Date(lowestDate + i * 24 * 60 * 60 * 1000) }.get(Calendar.DAY_OF_MONTH) val date = Calendar.getInstance().apply { this.time = Date(lowestDate + i * 24 * 60 * 60 * 1000) }.get(Calendar.DAY_OF_MONTH)
val str = when (i) { val str = when (i) {
@ -58,6 +58,7 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() {
val refDateHigh = Calendar.getInstance().apply { this.time = Date(lowestDate + 13 * 24 * 60 * 60 * 1000) }.get(Calendar.DAY_OF_MONTH) val refDateHigh = Calendar.getInstance().apply { this.time = Date(lowestDate + 13 * 24 * 60 * 60 * 1000) }.get(Calendar.DAY_OF_MONTH)
for (entry in database.rpiHistogram) { for (entry in database.rpiHistogram) {
val time = Date(entry.key * 24 * 60 * 60 * 1000) val time = Date(entry.key * 24 * 60 * 60 * 1000)
if (time.time < lowestDate) continue // Ignore old data
val date = Calendar.getInstance().apply { this.time = time }.get(Calendar.DAY_OF_MONTH) val date = Calendar.getInstance().apply { this.time = time }.get(Calendar.DAY_OF_MONTH)
val str = when (date) { val str = when (date) {
refDateLow, refDateHigh -> DateFormat.format(DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMd"), entry.key * 24 * 60 * 60 * 1000).toString() refDateLow, refDateHigh -> DateFormat.format(DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMd"), entry.key * 24 * 60 * 60 * 1000).toString()

View file

@ -30,9 +30,6 @@
<receiver android:name="org.microg.gms.nearby.exposurenotification.ServiceTrigger"> <receiver android:name="org.microg.gms.nearby.exposurenotification.ServiceTrigger">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.AIRPLANE_MODE" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" /> <action android:name="android.intent.action.PACKAGE_RESTARTED" />

View file

@ -9,7 +9,10 @@ import android.annotation.TargetApi
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.* import android.bluetooth.le.*
import android.bluetooth.le.AdvertiseSettings.* import android.bluetooth.le.AdvertiseSettings.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.util.Log import android.util.Log
import androidx.lifecycle.LifecycleService import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -25,10 +28,27 @@ import kotlin.coroutines.suspendCoroutine
@TargetApi(21) @TargetApi(21)
class AdvertiserService : LifecycleService() { class AdvertiserService : LifecycleService() {
private val version = VERSION_1_0 private val version = VERSION_1_0
private var looping = false
private var callback: AdvertiseCallback? = null private var callback: AdvertiseCallback? = null
private val advertiser: BluetoothLeAdvertiser private val advertiser: BluetoothLeAdvertiser?
get() = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser get() = BluetoothAdapter.getDefaultAdapter().bluetoothLeAdvertiser
private lateinit var database: ExposureDatabase private lateinit var database: ExposureDatabase
private val trigger = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_OFF -> stopAdvertising()
BluetoothAdapter.STATE_ON -> {
if (looping) {
lifecycleScope.launchWhenStarted { restartAdvertising() }
} else {
loopAdvertising()
}
}
}
}
}
}
private suspend fun BluetoothLeAdvertiser.startAdvertising(settings: AdvertiseSettings, advertiseData: AdvertiseData): AdvertiseCallback = suspendCoroutine { private suspend fun BluetoothLeAdvertiser.startAdvertising(settings: AdvertiseSettings, advertiseData: AdvertiseData): AdvertiseCallback = suspendCoroutine {
startAdvertising(settings, advertiseData, object : AdvertiseCallback() { startAdvertising(settings, advertiseData, object : AdvertiseCallback() {
@ -45,12 +65,13 @@ class AdvertiserService : LifecycleService() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
database = ExposureDatabase.ref(this) database = ExposureDatabase.ref(this)
registerReceiver(trigger, IntentFilter().also { it.addAction("android.bluetooth.adapter.action.STATE_CHANGED") })
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
if (ExposurePreferences(this).advertiserEnabled) { if (ExposurePreferences(this).advertiserEnabled) {
startAdvertising() loopAdvertising()
} else { } else {
stopSelf() stopSelf()
} }
@ -59,40 +80,63 @@ class AdvertiserService : LifecycleService() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
unregisterReceiver(trigger)
stopAdvertising() stopAdvertising()
database.unref() database.unref()
} }
fun startAdvertising() { @Synchronized
fun loopAdvertising() {
if (looping) return
looping = true
lifecycleScope.launchWhenStarted { lifecycleScope.launchWhenStarted {
do { Log.d(TAG, "Looping advertising")
val aem = when (version) { try {
VERSION_1_0 -> byteArrayOf( do {
version, // Version and flags val aem = when (version) {
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power VERSION_1_0 -> byteArrayOf(
0x00, // Reserved version, // Version and flags
0x00 // Reserved (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
) 0x00, // Reserved
VERSION_1_1 -> byteArrayOf( 0x00 // Reserved
(version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags )
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power VERSION_1_1 -> byteArrayOf(
0x00, // Reserved (version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags
0x00 // Reserved (currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
) 0x00, // Reserved
else -> return@launchWhenStarted 0x00 // Reserved
} )
val payload = database.generateCurrentPayload(aem) else -> return@launchWhenStarted
var nextSend = nextKeyMillis.coerceAtMost(180000) }
startAdvertising(payload, nextSend.toInt()) val payload = database.generateCurrentPayload(aem)
delay(nextSend) var nextSend = nextKeyMillis.coerceAtMost(180000)
} while (callback != null) startAdvertising(payload, nextSend.toInt())
if (callback != null) delay(nextSend)
} while (callback != null)
} catch (e: Exception) {
Log.w(TAG, "Error during advertising loop", e)
}
Log.d(TAG, "No longer advertising")
synchronized(this@AdvertiserService) {
looping = false
}
} }
} }
var startTime = System.currentTimeMillis()
var sendingBytes = ByteArray(0)
var sendingNext = 0
suspend fun startAdvertising(bytes: ByteArray, nextSend: Int) { suspend fun startAdvertising(bytes: ByteArray, nextSend: Int) {
startTime = System.currentTimeMillis()
sendingBytes = bytes
sendingNext = nextSend
continueAdvertising(bytes, nextSend)
}
private suspend fun continueAdvertising(bytes: ByteArray, nextSend: Int) {
stopAdvertising() stopAdvertising()
val data = AdvertiseData.Builder().addServiceUuid(SERVICE_UUID).addServiceData(SERVICE_UUID, bytes).build() val data = AdvertiseData.Builder().addServiceUuid(SERVICE_UUID).addServiceData(SERVICE_UUID, bytes).build()
val settings = AdvertiseSettings.Builder() val settings = Builder()
.setTimeout(nextSend) .setTimeout(nextSend)
.setAdvertiseMode(ADVERTISE_MODE_LOW_POWER) .setAdvertiseMode(ADVERTISE_MODE_LOW_POWER)
.setTxPowerLevel(ADVERTISE_TX_POWER_MEDIUM) .setTxPowerLevel(ADVERTISE_TX_POWER_MEDIUM)
@ -100,18 +144,42 @@ class AdvertiserService : LifecycleService() {
.build() .build()
val (uuid, aem) = ByteBuffer.wrap(bytes).let { UUID(it.long, it.long) to it.int } val (uuid, aem) = ByteBuffer.wrap(bytes).let { UUID(it.long, it.long) to it.int }
Log.d(TAG, "RPI: $uuid, Version: 0x${version.toString(16)}, TX Power: ${currentDeviceInfo.txPowerCorrection + TX_POWER_LOW}, AEM: 0x${aem.toLong().let { if (it < 0) 0x100000000L + it else it }.toString(16)}, Timeout: ${nextSend}ms") Log.d(TAG, "RPI: $uuid, Version: 0x${version.toString(16)}, TX Power: ${currentDeviceInfo.txPowerCorrection + TX_POWER_LOW}, AEM: 0x${aem.toLong().let { if (it < 0) 0x100000000L + it else it }.toString(16)}, Timeout: ${nextSend}ms")
callback = advertiser.startAdvertising(settings, data) callback = advertiser?.startAdvertising(settings, data)
}
suspend fun restartAdvertising() {
val startTime = startTime
val bytes = sendingBytes
val next = sendingNext
if (next == 0 || bytes.isEmpty()) return
val nextSend = (startTime - System.currentTimeMillis() + next).toInt()
if (nextSend < 5000) return
continueAdvertising(bytes, nextSend)
} }
override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) { override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
writer?.println("Looping: $looping")
writer?.println("Active: ${callback != null}") writer?.println("Active: ${callback != null}")
writer?.println("Currently advertising: ${database.currentRpiId}") try {
writer?.println("Next key change in ${nextKeyMillis}ms") val startTime = startTime
val bytes = sendingBytes
val (uuid, aem) = ByteBuffer.wrap(bytes).let { UUID(it.long, it.long) to it.int }
writer?.println("""
Last advertising:
Since: ${Date(startTime)}
RPI: $uuid
Version: 0x${version.toString(16)}
TX Power: ${currentDeviceInfo.txPowerCorrection + TX_POWER_LOW}
AEM: 0x${aem.toLong().let { if (it < 0) 0x100000000L + it else it }.toString(16)}
""".trimIndent())
} catch (e: Exception) {
writer?.println("Last advertising: ${e.message ?: e.toString()}")
}
} }
@Synchronized @Synchronized
fun stopAdvertising() { fun stopAdvertising() {
callback?.let { advertiser.stopAdvertising(it) } callback?.let { advertiser?.stopAdvertising(it) }
callback = null callback = null
} }
} }

View file

@ -23,8 +23,8 @@ val currentDeviceInfo: DeviceInfo
get() { get() {
var deviceInfo = knownDeviceInfo var deviceInfo = knownDeviceInfo
if (deviceInfo == null) { if (deviceInfo == null) {
val byOem = allDeviceInfos.filter { it.oem == Build.MANUFACTURER } val byOem = allDeviceInfos.filter { it.oem.equalsIgnoreCase(Build.MANUFACTURER) }
val exactMatch = byOem.find { it.model == Build.MODEL } val exactMatch = byOem.find { it.model.equalsIgnoreCase(Build.MODEL) }
deviceInfo = when { deviceInfo = when {
exactMatch != null -> { exactMatch != null -> {
// Exact match // Exact match
@ -45,6 +45,9 @@ val currentDeviceInfo: DeviceInfo
return deviceInfo return deviceInfo
} }
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private fun String.equalsIgnoreCase(other: String): Boolean = (this as java.lang.String).equalsIgnoreCase(other)
/* /*
* Derived from en-calibration-2020-06-13.csv published via * Derived from en-calibration-2020-06-13.csv published via
* https://developers.google.com/android/exposure-notifications/ble-attenuation-computation#device-list * https://developers.google.com/android/exposure-notifications/ble-attenuation-computation#device-list

View file

@ -8,42 +8,86 @@ package org.microg.gms.nearby.exposurenotification
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.Service import android.app.Service
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothAdapter.*
import android.bluetooth.le.* import android.bluetooth.le.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
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 java.io.FileDescriptor
import java.io.PrintWriter
import java.util.*
@TargetApi(21) @TargetApi(21)
class ScannerService : Service() { class ScannerService : Service() {
private var started = false private var started = false
private var startTime = 0L
private var seenAdvertisements = 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?) {
val data = result?.scanRecord?.serviceData?.get(SERVICE_UUID) ?: return result?.let { onScanResult(it) }
if (data.size < 16) return // Ignore invalid advertisements }
database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi)
override fun onBatchScanResults(results: MutableList<ScanResult>) {
Log.d(TAG, "onBatchScanResults: ${results.size}")
for (result in results) {
onScanResult(result)
}
}
override fun onScanFailed(errorCode: Int) {
Log.w(TAG, "onScanFailed: $errorCode")
stopScan()
} }
} }
private val scanner: BluetoothLeScanner private val trigger = object : BroadcastReceiver() {
get() = BluetoothAdapter.getDefaultAdapter().bluetoothLeScanner override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") {
when (intent.getIntExtra(EXTRA_STATE, -1)) {
STATE_TURNING_OFF, STATE_OFF -> stopScan()
STATE_ON -> startScanIfNeeded()
}
}
}
}
private val scanner: BluetoothLeScanner?
get() = getDefaultAdapter().bluetoothLeScanner
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
startScanIfNeeded()
return START_STICKY
}
fun onScanResult(result: ScanResult) {
val data = result.scanRecord?.serviceData?.get(SERVICE_UUID) ?: return
if (data.size < 16) return // Ignore invalid advertisements
database.noteAdvertisement(data.sliceArray(0..15), data.drop(16).toByteArray(), result.rssi)
seenAdvertisements++
}
fun startScanIfNeeded() {
if (ExposurePreferences(this).scannerEnabled) { if (ExposurePreferences(this).scannerEnabled) {
startScan() startScan()
} else { } else {
stopSelf() stopSelf()
} }
return START_STICKY
} }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
database = ExposureDatabase.ref(this) database = ExposureDatabase.ref(this)
registerReceiver(trigger, IntentFilter().also { it.addAction("android.bluetooth.adapter.action.STATE_CHANGED") })
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
unregisterReceiver(trigger)
stopScan() stopScan()
database.unref() database.unref()
} }
@ -55,6 +99,8 @@ class ScannerService : Service() {
@Synchronized @Synchronized
private fun startScan() { private fun startScan() {
if (started) return if (started) return
val scanner = scanner ?: return
Log.d(TAG, "Starting scanner for service $SERVICE_UUID")
scanner.startScan( scanner.startScan(
listOf(ScanFilter.Builder() listOf(ScanFilter.Builder()
.setServiceUuid(SERVICE_UUID) .setServiceUuid(SERVICE_UUID)
@ -66,12 +112,22 @@ class ScannerService : Service() {
callback callback
) )
started = true started = true
startTime = System.currentTimeMillis()
} }
@Synchronized @Synchronized
private fun stopScan() { private fun stopScan() {
if (!started) return if (!started) return
scanner.stopScan(callback) Log.d(TAG, "Stopping scanner for service $SERVICE_UUID")
started = false started = false
scanner?.stopScan(callback)
}
override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
writer?.println("Started: $started")
if (started) {
writer?.println("Since ${Date(startTime)}")
writer?.println("Seen advertisements: $seenAdvertisements")
}
} }
} }

View file

@ -9,11 +9,13 @@ import android.annotation.SuppressLint
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import org.microg.gms.common.ForegroundServiceContext import org.microg.gms.common.ForegroundServiceContext
class ServiceTrigger : BroadcastReceiver() { 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")
if (ExposurePreferences(context).scannerEnabled) { if (ExposurePreferences(context).scannerEnabled) {
ForegroundServiceContext(context).startService(Intent(context, ScannerService::class.java)) ForegroundServiceContext(context).startService(Intent(context, ScannerService::class.java))
} }