mirror of
https://github.com/YTVanced/VancedMicroG
synced 2024-11-30 23:23:01 +00:00
EN: Support SDK 26+ AdvertisingSet, use scheduled alarms for improved scanning in idle
This commit is contained in:
parent
f10214ef8a
commit
da9a3e714d
3 changed files with 168 additions and 125 deletions
|
@ -6,66 +6,61 @@
|
||||||
package org.microg.gms.nearby.exposurenotification
|
package org.microg.gms.nearby.exposurenotification
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.PendingIntent.FLAG_ONE_SHOT
|
||||||
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
import android.app.Service
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.bluetooth.le.AdvertiseCallback
|
import android.bluetooth.le.*
|
||||||
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
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.LifecycleService
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import org.microg.gms.common.ForegroundServiceContext
|
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
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.resumeWithException
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
class AdvertiserService : LifecycleService() {
|
class AdvertiserService : Service() {
|
||||||
private val version = VERSION_1_0
|
private val version = VERSION_1_0
|
||||||
private var looping = false
|
private var advertising = false
|
||||||
private var callback: AdvertiseCallback? = null
|
private var wantStartAdvertising = false
|
||||||
private val advertiser: BluetoothLeAdvertiser?
|
private val advertiser: BluetoothLeAdvertiser?
|
||||||
get() = BluetoothAdapter.getDefaultAdapter()?.bluetoothLeAdvertiser
|
get() = BluetoothAdapter.getDefaultAdapter()?.bluetoothLeAdvertiser
|
||||||
|
private val alarmManager: AlarmManager
|
||||||
|
get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
private lateinit var database: ExposureDatabase
|
private lateinit var database: ExposureDatabase
|
||||||
|
private val callback: AdvertiseCallback = object : AdvertiseCallback() {
|
||||||
|
override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) {
|
||||||
|
Log.d(TAG, "Advertising active for ${settingsInEffect?.timeout}ms")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartFailure(errorCode: Int) {
|
||||||
|
Log.w(TAG, "Advertising failed: $errorCode")
|
||||||
|
stopOrRestartAdvertising()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(23)
|
||||||
|
private var setCallback: AdvertisingSetCallback? = null
|
||||||
private val trigger = object : BroadcastReceiver() {
|
private val trigger = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") {
|
if (intent?.action == "android.bluetooth.adapter.action.STATE_CHANGED") {
|
||||||
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
|
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
|
||||||
BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_OFF -> stopAdvertising()
|
BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_OFF -> stopOrRestartAdvertising()
|
||||||
BluetoothAdapter.STATE_ON -> {
|
BluetoothAdapter.STATE_ON -> startAdvertisingIfNeeded()
|
||||||
if (looping) {
|
|
||||||
lifecycleScope.launchWhenStarted { restartAdvertising() }
|
|
||||||
} else {
|
|
||||||
loopAdvertising()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private suspend fun BluetoothLeAdvertiser.startAdvertising(settings: AdvertiseSettings, advertiseData: AdvertiseData): AdvertiseCallback = suspendCoroutine {
|
private val startLaterRunnable = Runnable { startAdvertisingIfNeeded() }
|
||||||
startAdvertising(settings, advertiseData, object : AdvertiseCallback() {
|
|
||||||
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
|
|
||||||
it.resume(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartFailure(errorCode: Int) {
|
|
||||||
it.resumeWithException(RuntimeException("Error code: $errorCode"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
@ -76,10 +71,10 @@ 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)
|
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
if (ExposurePreferences(this).enabled) {
|
if (intent?.action == ACTION_RESTART_ADVERTISING && advertising) {
|
||||||
loopAdvertising()
|
stopOrRestartAdvertising()
|
||||||
} else {
|
} else {
|
||||||
stopSelf()
|
startAdvertisingIfNeeded()
|
||||||
}
|
}
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
@ -87,87 +82,79 @@ class AdvertiserService : LifecycleService() {
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
unregisterReceiver(trigger)
|
unregisterReceiver(trigger)
|
||||||
stopAdvertising()
|
stopOrRestartAdvertising()
|
||||||
database.unref()
|
database.unref()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
fun loopAdvertising() {
|
return null
|
||||||
if (looping) return
|
}
|
||||||
looping = true
|
|
||||||
lifecycleScope.launchWhenStarted {
|
fun startAdvertisingIfNeeded() {
|
||||||
Log.d(TAG, "Looping advertising")
|
if (ExposurePreferences(this).enabled) {
|
||||||
try {
|
startAdvertising()
|
||||||
do {
|
} else {
|
||||||
val aem = when (version) {
|
stopSelf()
|
||||||
VERSION_1_0 -> byteArrayOf(
|
|
||||||
version, // Version and flags
|
|
||||||
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
|
|
||||||
0x00, // Reserved
|
|
||||||
0x00 // Reserved
|
|
||||||
)
|
|
||||||
VERSION_1_1 -> byteArrayOf(
|
|
||||||
(version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags
|
|
||||||
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
|
|
||||||
0x00, // Reserved
|
|
||||||
0x00 // Reserved
|
|
||||||
)
|
|
||||||
else -> return@launchWhenStarted
|
|
||||||
}
|
|
||||||
val payload = database.generateCurrentPayload(aem)
|
|
||||||
val nextSend = (nextKeyMillis + Random.nextInt(-ADVERTISER_OFFSET, ADVERTISER_OFFSET)).coerceIn(0, 180000)
|
|
||||||
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()
|
private var lastStartTime = System.currentTimeMillis()
|
||||||
var sendingBytes = ByteArray(0)
|
private var sendingBytes = ByteArray(0)
|
||||||
var sendingNext = 0
|
|
||||||
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) {
|
@Synchronized
|
||||||
stopAdvertising()
|
fun startAdvertising() {
|
||||||
val data = AdvertiseData.Builder().addServiceUuid(SERVICE_UUID).addServiceData(SERVICE_UUID, bytes).build()
|
if (advertising) return
|
||||||
val settings = Builder()
|
val advertiser = advertiser ?: return
|
||||||
.setTimeout(nextSend)
|
wantStartAdvertising = false
|
||||||
.setAdvertiseMode(ADVERTISE_MODE_LOW_POWER)
|
val aemBytes = when (version) {
|
||||||
.setTxPowerLevel(ADVERTISE_TX_POWER_MEDIUM)
|
VERSION_1_0 -> byteArrayOf(
|
||||||
.setConnectable(false)
|
version, // Version and flags
|
||||||
.build()
|
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
|
||||||
val (uuid, aem) = ByteBuffer.wrap(bytes).let { UUID(it.long, it.long) to it.int }
|
0x00, // Reserved
|
||||||
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")
|
0x00 // Reserved
|
||||||
callback = advertiser?.startAdvertising(settings, data)
|
)
|
||||||
}
|
VERSION_1_1 -> byteArrayOf(
|
||||||
|
(version + currentDeviceInfo.confidence * 4).toByte(), // Version and flags
|
||||||
suspend fun restartAdvertising() {
|
(currentDeviceInfo.txPowerCorrection + TX_POWER_LOW).toByte(), // TX power
|
||||||
val startTime = startTime
|
0x00, // Reserved
|
||||||
val bytes = sendingBytes
|
0x00 // Reserved
|
||||||
val next = sendingNext
|
)
|
||||||
if (next == 0 || bytes.isEmpty()) return
|
else -> return
|
||||||
val nextSend = (startTime - System.currentTimeMillis() + next).toInt()
|
}
|
||||||
if (nextSend < 5000) return
|
var nextSend = nextKeyMillis.coerceAtLeast(10000)
|
||||||
continueAdvertising(bytes, nextSend)
|
val payload = database.generateCurrentPayload(aemBytes)
|
||||||
|
val data = AdvertiseData.Builder().addServiceUuid(SERVICE_UUID).addServiceData(SERVICE_UUID, payload).build()
|
||||||
|
val (uuid, _) = ByteBuffer.wrap(payload).let { UUID(it.long, it.long) to it.int }
|
||||||
|
Log.i(TAG, "Starting advertiser for RPI $uuid")
|
||||||
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
setCallback = SetCallback()
|
||||||
|
val params = AdvertisingSetParameters.Builder()
|
||||||
|
.setInterval(AdvertisingSetParameters.INTERVAL_MEDIUM)
|
||||||
|
.setLegacyMode(true)
|
||||||
|
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_LOW)
|
||||||
|
.setConnectable(false)
|
||||||
|
.build()
|
||||||
|
advertiser.startAdvertisingSet(params, data, null, null, null, setCallback)
|
||||||
|
} else {
|
||||||
|
nextSend = nextSend.coerceAtMost(180000)
|
||||||
|
val settings = Builder()
|
||||||
|
.setTimeout(nextSend.toInt())
|
||||||
|
.setAdvertiseMode(ADVERTISE_MODE_BALANCED)
|
||||||
|
.setTxPowerLevel(ADVERTISE_TX_POWER_LOW)
|
||||||
|
.setConnectable(false)
|
||||||
|
.build()
|
||||||
|
advertiser.startAdvertising(settings, data, callback)
|
||||||
|
}
|
||||||
|
advertising = true
|
||||||
|
sendingBytes = payload
|
||||||
|
lastStartTime = System.currentTimeMillis()
|
||||||
|
scheduleRestartAdvertising(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("Advertising: $advertising")
|
||||||
writer?.println("Active: ${callback != null}")
|
|
||||||
try {
|
try {
|
||||||
val startTime = startTime
|
val startTime = lastStartTime
|
||||||
val bytes = sendingBytes
|
val bytes = sendingBytes
|
||||||
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 }
|
||||||
writer?.println("""
|
writer?.println("""
|
||||||
|
@ -183,15 +170,53 @@ class AdvertiserService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun scheduleRestartAdvertising(nextSend: Long) {
|
||||||
|
val intent = Intent(this, AdvertiserService::class.java).apply { action = ACTION_RESTART_ADVERTISING }
|
||||||
|
val pendingIntent = PendingIntent.getService(this, ACTION_RESTART_ADVERTISING.hashCode(), intent, FLAG_ONE_SHOT and FLAG_UPDATE_CURRENT)
|
||||||
|
when {
|
||||||
|
Build.VERSION.SDK_INT >= 23 ->
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + nextSend, pendingIntent)
|
||||||
|
else ->
|
||||||
|
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + nextSend, pendingIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun stopAdvertising() {
|
fun stopOrRestartAdvertising() {
|
||||||
callback?.let { advertiser?.stopAdvertising(it) }
|
if (!advertising) return
|
||||||
callback = null
|
val (uuid, _) = ByteBuffer.wrap(sendingBytes).let { UUID(it.long, it.long) to it.int }
|
||||||
|
Log.i(TAG, "Stopping advertiser for RPI $uuid")
|
||||||
|
advertising = false
|
||||||
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
wantStartAdvertising = true
|
||||||
|
advertiser?.stopAdvertisingSet(setCallback)
|
||||||
|
} else {
|
||||||
|
advertiser?.stopAdvertising(callback)
|
||||||
|
}
|
||||||
|
handler.postDelayed(startLaterRunnable, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val ACTION_RESTART_ADVERTISING = "org.microg.gms.nearby.exposurenotification.RESTART_ADVERTISING"
|
||||||
|
|
||||||
fun isNeeded(context: Context): Boolean {
|
fun isNeeded(context: Context): Boolean {
|
||||||
return ExposurePreferences(context).enabled
|
return ExposurePreferences(context).enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(26)
|
||||||
|
inner class SetCallback : AdvertisingSetCallback() {
|
||||||
|
override fun onAdvertisingSetStarted(advertisingSet: AdvertisingSet?, txPower: Int, status: Int) {
|
||||||
|
Log.d(TAG, "Advertising active, status=$status")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdvertisingSetStopped(advertisingSet: AdvertisingSet?) {
|
||||||
|
Log.d(TAG, "Advertising stopped")
|
||||||
|
if (wantStartAdvertising) {
|
||||||
|
startAdvertisingIfNeeded()
|
||||||
|
} else {
|
||||||
|
stopOrRestartAdvertising()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ val currentDeviceInfo: DeviceInfo
|
||||||
averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CONFIDENCE_LOWEST)
|
averageCurrentDeviceInfo(Build.MANUFACTURER, Build.MODEL, allDeviceInfos, CONFIDENCE_LOWEST)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Selected $deviceInfo")
|
Log.i(TAG, "Selected $deviceInfo")
|
||||||
knownDeviceInfo = deviceInfo
|
knownDeviceInfo = deviceInfo
|
||||||
}
|
}
|
||||||
return deviceInfo
|
return deviceInfo
|
||||||
|
|
|
@ -5,7 +5,10 @@
|
||||||
|
|
||||||
package org.microg.gms.nearby.exposurenotification
|
package org.microg.gms.nearby.exposurenotification
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.bluetooth.BluetoothAdapter.*
|
import android.bluetooth.BluetoothAdapter.*
|
||||||
import android.bluetooth.le.*
|
import android.bluetooth.le.*
|
||||||
|
@ -13,10 +16,7 @@ import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.*
|
||||||
import android.os.Handler
|
|
||||||
import android.os.IBinder
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.microg.gms.common.ForegroundServiceContext
|
import org.microg.gms.common.ForegroundServiceContext
|
||||||
import java.io.FileDescriptor
|
import java.io.FileDescriptor
|
||||||
|
@ -36,7 +36,6 @@ class ScannerService : Service() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBatchScanResults(results: MutableList<ScanResult>) {
|
override fun onBatchScanResults(results: MutableList<ScanResult>) {
|
||||||
Log.d(TAG, "onBatchScanResults: ${results.size}")
|
|
||||||
for (result in results) {
|
for (result in results) {
|
||||||
onScanResult(result)
|
onScanResult(result)
|
||||||
}
|
}
|
||||||
|
@ -59,10 +58,19 @@ class ScannerService : Service() {
|
||||||
}
|
}
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private val stopLaterRunnable = Runnable { stopScan() }
|
private val stopLaterRunnable = Runnable { stopScan() }
|
||||||
private val startLaterRunnable = Runnable { startScan() }
|
|
||||||
|
// Wake lock for the duration of scan. Otherwise we might fall asleep while scanning
|
||||||
|
// resulting in potentially very long scan times
|
||||||
|
private val wakeLock: PowerManager.WakeLock by lazy {
|
||||||
|
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, ScannerService::class.java.canonicalName).apply { setReferenceCounted(false) }
|
||||||
|
}
|
||||||
|
|
||||||
private val scanner: BluetoothLeScanner?
|
private val scanner: BluetoothLeScanner?
|
||||||
get() = getDefaultAdapter()?.bluetoothLeScanner
|
get() = getDefaultAdapter()?.bluetoothLeScanner
|
||||||
|
private val alarmManager: AlarmManager
|
||||||
|
get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
private val powerManager: PowerManager
|
||||||
|
get() = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
|
||||||
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)
|
ForegroundServiceContext.completeForegroundService(this, intent, TAG)
|
||||||
|
@ -104,21 +112,20 @@ class ScannerService : Service() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("WakelockTimeout")
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun startScan() {
|
private fun startScan() {
|
||||||
if (scanning) return
|
if (scanning) return
|
||||||
val scanner = scanner ?: return
|
val scanner = scanner ?: return
|
||||||
Log.d(TAG, "Starting scanner for service $SERVICE_UUID")
|
Log.i(TAG, "Starting scanner for service $SERVICE_UUID for ${SCANNING_TIME_MS}ms")
|
||||||
handler.removeCallbacks(startLaterRunnable)
|
|
||||||
seenAdvertisements = 0
|
seenAdvertisements = 0
|
||||||
|
wakeLock.acquire()
|
||||||
scanner.startScan(
|
scanner.startScan(
|
||||||
listOf(ScanFilter.Builder()
|
listOf(ScanFilter.Builder()
|
||||||
.setServiceUuid(SERVICE_UUID)
|
.setServiceUuid(SERVICE_UUID)
|
||||||
.setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0))
|
.setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0))
|
||||||
.build()),
|
.build()),
|
||||||
ScanSettings.Builder()
|
ScanSettings.Builder().build(),
|
||||||
.let { if (Build.VERSION.SDK_INT >= 23) it.setMatchMode(ScanSettings.MATCH_MODE_STICKY) else it }
|
|
||||||
.build(),
|
|
||||||
callback
|
callback
|
||||||
)
|
)
|
||||||
scanning = true
|
scanning = true
|
||||||
|
@ -129,12 +136,24 @@ class ScannerService : Service() {
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun stopScan() {
|
private fun stopScan() {
|
||||||
if (!scanning) return
|
if (!scanning) return
|
||||||
Log.d(TAG, "Stopping scanner for service $SERVICE_UUID")
|
Log.i(TAG, "Stopping scanner for service $SERVICE_UUID, had seen $seenAdvertisements advertisements")
|
||||||
handler.removeCallbacks(stopLaterRunnable)
|
handler.removeCallbacks(stopLaterRunnable)
|
||||||
scanning = false
|
scanning = false
|
||||||
scanner?.stopScan(callback)
|
scanner?.stopScan(callback)
|
||||||
if (ExposurePreferences(this).enabled) {
|
if (ExposurePreferences(this).enabled) {
|
||||||
handler.postDelayed(startLaterRunnable, ((lastStartTime + SCANNING_INTERVAL_MS) - System.currentTimeMillis()).coerceIn(0, SCANNING_INTERVAL_MS))
|
scheduleStartScan(((lastStartTime + SCANNING_INTERVAL_MS) - System.currentTimeMillis()).coerceIn(0, SCANNING_INTERVAL_MS))
|
||||||
|
}
|
||||||
|
wakeLock.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleStartScan(nextScan: Long) {
|
||||||
|
val intent = Intent(this, ScannerService::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getService(this, ScannerService::class.java.hashCode(), intent, PendingIntent.FLAG_ONE_SHOT and PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
// Note: there is no setWindowAndAllowWhileIdle()
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + nextScan, pendingIntent)
|
||||||
|
} else {
|
||||||
|
alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + nextScan - SCANNING_TIME_MS / 2, SCANNING_TIME_MS, pendingIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +161,6 @@ class ScannerService : Service() {
|
||||||
writer?.println("Scanning now: $scanning")
|
writer?.println("Scanning now: $scanning")
|
||||||
writer?.println("Last scan start: ${Date(lastStartTime)}")
|
writer?.println("Last scan start: ${Date(lastStartTime)}")
|
||||||
if (Build.VERSION.SDK_INT >= 29) {
|
if (Build.VERSION.SDK_INT >= 29) {
|
||||||
writer?.println("Scan start pending: ${handler.hasCallbacks(startLaterRunnable)}")
|
|
||||||
writer?.println("Scan stop pending: ${handler.hasCallbacks(stopLaterRunnable)}")
|
writer?.println("Scan stop pending: ${handler.hasCallbacks(stopLaterRunnable)}")
|
||||||
}
|
}
|
||||||
writer?.println("Seen advertisements since last scan start: $seenAdvertisements")
|
writer?.println("Seen advertisements since last scan start: $seenAdvertisements")
|
||||||
|
|
Loading…
Reference in a new issue