mirror of
https://github.com/AU-COVIDSafe/mobile-ios.git
synced 2025-04-04 22:04:59 +00:00
228 lines
9 KiB
Swift
228 lines
9 KiB
Swift
import UIKit
|
|
import CoreData
|
|
import CoreBluetooth
|
|
|
|
class BluetraceManager: SensorDelegate {
|
|
|
|
private let logger = ConcreteSensorLogger(subsystem: "Herald", category: "BluetraceManager")
|
|
|
|
var sensorDidUpdateStateCallback: ((SensorState, SensorType?) -> Void)?
|
|
|
|
// Payload data supplier, sensor and contact log
|
|
var payloadDataSupplier: PayloadDataSupplier?
|
|
var sensor: Sensor?
|
|
|
|
static let shared = BluetraceManager()
|
|
|
|
var bleSensorState: SensorState = .unavailable
|
|
var awakeSensorState: SensorState = .unavailable
|
|
|
|
private var didRecordPayloadData: [String:Date] = [:]
|
|
private var didWriteLegacyPayloadData: [String:Date] = [:]
|
|
|
|
func turnOnBLE() {
|
|
payloadDataSupplier = EncounterMessageManager.shared
|
|
if sensor == nil {
|
|
sensor = SensorArray(payloadDataSupplier!)
|
|
sensor?.add(delegate: self)
|
|
}
|
|
sensor?.start()
|
|
}
|
|
|
|
func turnOnAllSensors() {
|
|
payloadDataSupplier = EncounterMessageManager.shared
|
|
if sensor == nil {
|
|
sensor = SensorArray(payloadDataSupplier!)
|
|
sensor?.add(delegate: self)
|
|
// we are going to turn on one at a time
|
|
let previousSensorDidUpdateStateCallback = sensorDidUpdateStateCallback
|
|
sensorDidUpdateStateCallback = { state, type in
|
|
if (state == .on ) {
|
|
self.sensorDidUpdateStateCallback = previousSensorDidUpdateStateCallback
|
|
self.turnOnLocationSensor()
|
|
}
|
|
}
|
|
}
|
|
sensor?.start()
|
|
}
|
|
|
|
func addDelegateToSensors(delegate: SensorDelegate) {
|
|
sensor?.add(delegate: delegate)
|
|
}
|
|
|
|
func turnOnLocationSensor() {
|
|
guard let sensorArray = sensor as? SensorArray else {
|
|
return
|
|
}
|
|
DispatchQueue.main.async {
|
|
sensorArray.startAwakeSensor()
|
|
}
|
|
}
|
|
|
|
func isBluetoothAuthorized() -> Bool {
|
|
if #available(iOS 13.1, *) {
|
|
return CBManager.authorization == .allowedAlways
|
|
} else {
|
|
return CBPeripheralManager.authorizationStatus() == .authorized
|
|
}
|
|
}
|
|
|
|
func isBluetoothOn() -> Bool {
|
|
return bleSensorState == .on
|
|
}
|
|
|
|
func isLocationOnAuthorized() -> Bool {
|
|
return awakeSensorState == .on
|
|
}
|
|
|
|
func centralDidUpdateStateCallback(_ state: CBManagerState) {
|
|
if state == .poweredOn {
|
|
sensorDidUpdateStateCallback?(.on, .BLE)
|
|
} else {
|
|
sensorDidUpdateStateCallback?(.off, .BLE)
|
|
}
|
|
}
|
|
|
|
func toggleAdvertisement(_ state: Bool) {
|
|
if state {
|
|
sensor?.start()
|
|
} else {
|
|
sensor?.stop()
|
|
}
|
|
}
|
|
|
|
func toggleScanning(_ state: Bool) {
|
|
if state {
|
|
sensor?.start()
|
|
} else {
|
|
sensor?.stop()
|
|
}
|
|
}
|
|
|
|
func cleanRecordsData( records: inout [String:Date], expiryInterval: TimeInterval) {
|
|
let removeDataBefore = Date() - expiryInterval
|
|
let recentDidWritePayloadData = records.filter({ $0.value >= removeDataBefore })
|
|
records = recentDidWritePayloadData
|
|
}
|
|
|
|
func saveEncounter(payload: PayloadData, proximity: Proximity, txPower: Int? ) throws {
|
|
let peripheralCharData = try JSONDecoder().decode(PeripheralCharacteristicsData.self, from: payload)
|
|
var encounterStruct = EncounterRecord(rssi: proximity.value, txPower: txPower == nil ? nil : Double(txPower!))
|
|
encounterStruct.msg = peripheralCharData.msg
|
|
encounterStruct.update(modelP: peripheralCharData.modelP)
|
|
encounterStruct.org = peripheralCharData.org
|
|
encounterStruct.v = peripheralCharData.v
|
|
encounterStruct.timestamp = Date()
|
|
// here the remote blob will be msg and modelp if v1, msg if v2
|
|
// local blob will be rssi, txpower, modelc
|
|
try encounterStruct.saveRemotePeripheralToCoreData()
|
|
}
|
|
|
|
func getIdentifier(forDevice: BLEDevice) -> String {
|
|
return forDevice.pseudoDeviceAddress != nil ? "\(forDevice.pseudoDeviceAddress!.address)" : forDevice.identifier
|
|
}
|
|
|
|
func shouldSaveEncounter(forDeviceIdentifier: String) -> Bool {
|
|
|
|
guard didRecordPayloadData[forDeviceIdentifier] == nil || abs(didRecordPayloadData[forDeviceIdentifier]!.timeIntervalSinceNow) > BluetraceConfig.PeripheralPayloadSaveInterval else {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// MARK:- SensorDelegate
|
|
|
|
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {
|
|
logger.info(sensor.rawValue + ",didDetect=" + didDetect.description)
|
|
}
|
|
|
|
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {
|
|
logger.info(sensor.rawValue + ",didMeasure=" + didMeasure.description + ",fromTarget=" + fromTarget.description)
|
|
|
|
// Make a call to the messages API if needed.
|
|
// Dispatch in background queue
|
|
DispatchQueue.global(qos: .background).async {
|
|
MessageAPI.getMessagesIfNeeded() { (messageResponse, error) in
|
|
if let error = error {
|
|
DLog("Get messages error: \(error.localizedDescription)")
|
|
}
|
|
// We currently dont do anything with the response. Messages are delivered via APN
|
|
}
|
|
}
|
|
}
|
|
|
|
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier, withPayload: PayloadData, forDevice: BLEDevice) {
|
|
logger.info(sensor.rawValue + ",didMeasure=" + didMeasure.description + ",fromTarget=" + fromTarget.description + ",withPayload=" + withPayload.shortName)
|
|
|
|
let deviceIdentifier = getIdentifier(forDevice: forDevice)
|
|
guard shouldSaveEncounter(forDeviceIdentifier: deviceIdentifier) else {
|
|
logger.info("didMeasure, skipping save as time since last saved too recet fromTarget=" + fromTarget.description)
|
|
return
|
|
}
|
|
didRecordPayloadData[deviceIdentifier] = Date()
|
|
cleanRecordsData(records: &didRecordPayloadData, expiryInterval: BluetraceConfig.PeripheralPayloadExpiry)
|
|
do {
|
|
logger.debug("Saving on didMeasure fromTarget=" + fromTarget.description)
|
|
try saveEncounter(payload: withPayload, proximity: didMeasure, txPower: nil)
|
|
} catch {
|
|
logger.fault("ERROR "+sensor.rawValue + ",didMeasure=" + didMeasure.description + ",fromTarget=" + fromTarget.description + ",withPayload=" + withPayload.shortName )
|
|
}
|
|
|
|
}
|
|
|
|
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {
|
|
do {
|
|
let dataFromCentral = try JSONDecoder().decode(CentralWriteData.self, from: didRead)
|
|
logger.info(sensor.rawValue + ",didRead=" + dataFromCentral.msg + ",fromTarget=" + fromTarget.description)
|
|
} catch {
|
|
logger.fault(sensor.rawValue + ",didRead=" + didRead.shortName + ",fromTarget=" + fromTarget.description)
|
|
}
|
|
}
|
|
|
|
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {
|
|
logger.info(sensor.rawValue + ",didRead=\(didRead.shortName))" + ",fromTarget=" + fromTarget.description + ",atProximity=" + atProximity.description + ",withTxPower=\(String(describing: withTxPower))")
|
|
}
|
|
|
|
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {
|
|
let payloads = didShare.map { $0.shortName }
|
|
logger.info(sensor.rawValue + ",didShare=" + payloads.description + ",fromTarget=" + fromTarget.description)
|
|
|
|
}
|
|
|
|
func sensor(_ sensor: SensorType, didUpdateState: SensorState) {
|
|
logger.info(sensor.rawValue + ",didUpdateState=" + didUpdateState.rawValue)
|
|
|
|
if sensor == .BLE {
|
|
bleSensorState = didUpdateState
|
|
sensorDidUpdateStateCallback?(didUpdateState, sensor)
|
|
}
|
|
|
|
if sensor == .AWAKE {
|
|
awakeSensorState = didUpdateState
|
|
sensorDidUpdateStateCallback?(didUpdateState, sensor)
|
|
}
|
|
}
|
|
|
|
func shouldWriteToLegacyDevice(_ device: BLEDevice) -> Bool {
|
|
|
|
guard device.legacyPayloadCharacteristic != nil &&
|
|
device.payloadCharacteristic == nil else {
|
|
return false
|
|
}
|
|
|
|
let cleanInterval = BluetraceConfig.PeripheralPayloadExpiry
|
|
let writeInterval = BluetraceConfig.PeripheralPayloadSaveInterval
|
|
let deviceIdentifier = getIdentifier(forDevice: device)
|
|
cleanRecordsData(records: &didWriteLegacyPayloadData, expiryInterval: cleanInterval)
|
|
|
|
guard didWriteLegacyPayloadData[deviceIdentifier] == nil || abs(didWriteLegacyPayloadData[deviceIdentifier]!.timeIntervalSinceNow) > writeInterval else {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func didWriteToLegacyDevice(_ device: BLEDevice) {
|
|
let deviceIdentifier = getIdentifier(forDevice: device)
|
|
didWriteLegacyPayloadData[deviceIdentifier] = Date()
|
|
}
|
|
}
|