From ea5bd7b84328fff9face5e8c9133b463367d2221 Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 07:27:00 +0930 Subject: [PATCH 1/9] Factor out ENTemporaryExposureKey --- .../Classes/ContactTraceManager.swift | 34 ++++++++-- TracePrivately/Classes/DataManager.swift | 23 ++++--- TracePrivately/Classes/ENMockFramework.swift | 1 + .../Classes/KeyServer/KeyServer.swift | 14 ++-- .../SettingsViewController.swift | 3 + .../SubmitInfectionViewController.swift | 65 ++++++++----------- 6 files changed, 84 insertions(+), 56 deletions(-) diff --git a/TracePrivately/Classes/ContactTraceManager.swift b/TracePrivately/Classes/ContactTraceManager.swift index bcd499b..72a33e4 100644 --- a/TracePrivately/Classes/ContactTraceManager.swift +++ b/TracePrivately/Classes/ContactTraceManager.swift @@ -137,9 +137,11 @@ extension ContactTraceManager { } } - fileprivate func addAndFinalizeKeys(session: ENExposureDetectionSession, keys: [ENTemporaryExposureKey], completion: @escaping (Swift.Error?) -> Void) { + fileprivate func addAndFinalizeKeys(session: ENExposureDetectionSession, keys: [DataManager.TemporaryExposureKey], completion: @escaping (Swift.Error?) -> Void) { - session.batchAddDiagnosisKeys(inKeys: keys) { error in + let k: [ENTemporaryExposureKey] = keys.map { .init(keyData: $0.keyData, rollingStartNumber: $0.rollingStartNumber) } + + session.batchAddDiagnosisKeys(inKeys: k) { error in session.finishedDiagnosisKeysWithCompletion { summary, error in guard let summary = summary else { completion(error) @@ -198,8 +200,11 @@ extension ContactTraceManager { } } - private func saveNewInfectedKeys(keys: [ENTemporaryExposureKey], completion: @escaping (_ numNewRemoteKeys: Int, Swift.Error?) -> Void) { - DataManager.shared.saveInfectedKeys(keys: keys) { numNewKeys, error in + private func saveNewInfectedKeys(keys: [DataManager.TemporaryExposureKey], completion: @escaping (_ numNewRemoteKeys: Int, Swift.Error?) -> Void) { + + let k: [DataManager.TemporaryExposureKey] = keys.map { .init(keyData: $0.keyData, rollingStartNumber: $0.rollingStartNumber) } + + DataManager.shared.saveInfectedKeys(keys: k) { numNewKeys, error in if let error = error { completion(0, error) return @@ -488,3 +493,24 @@ extension ENExposureDetectionSession { // } } } + +extension ContactTraceManager { + func retrieveSelfDiagnosisKeys(completion: @escaping ([DataManager.TemporaryExposureKey]?, Swift.Error?) -> Void) { + let request = ENSelfExposureInfoRequest() + + request.activateWithCompletion { error in + defer { + request.invalidate() + } + + guard let keys = request.selfExposureInfo?.keys else { + completion(nil, error) + return + } + + let k: [DataManager.TemporaryExposureKey] = keys.map { .init(keyData: $0.keyData, rollingStartNumber: $0.rollingStartNumber) } + + completion(k, nil) + } + } +} diff --git a/TracePrivately/Classes/DataManager.swift b/TracePrivately/Classes/DataManager.swift index 173f311..028ffb2 100644 --- a/TracePrivately/Classes/DataManager.swift +++ b/TracePrivately/Classes/DataManager.swift @@ -101,8 +101,15 @@ extension DataManager { } } } + + // TODO: Refactor this a bit more neatly + struct TemporaryExposureKey { + let keyData: Data + let rollingStartNumber: ENIntervalNumber + // TODO: Support risk level + } - func saveInfectedKeys(keys: [ENTemporaryExposureKey], completion: @escaping (_ numNewKeys: Int, _ error: Swift.Error?) -> Void) { + func saveInfectedKeys(keys: [TemporaryExposureKey], completion: @escaping (_ numNewKeys: Int, _ error: Swift.Error?) -> Void) { guard keys.count > 0 else { completion(0, nil) @@ -204,7 +211,7 @@ extension DataManager { } } - func allInfectedKeys(completion: @escaping ([ENTemporaryExposureKey]?, Swift.Error?) -> Void) { + func allInfectedKeys(completion: @escaping ([TemporaryExposureKey]?, Swift.Error?) -> Void) { let context = self.persistentContainer.newBackgroundContext() context.perform { @@ -213,7 +220,7 @@ extension DataManager { do { let entities = try context.fetch(request) - let keys: [ENTemporaryExposureKey] = entities.compactMap { $0.temporaryExposureKey } + let keys: [TemporaryExposureKey] = entities.compactMap { $0.temporaryExposureKey } completion(keys, nil) } catch { @@ -224,12 +231,12 @@ extension DataManager { } extension RemoteInfectedKeyEntity { - var temporaryExposureKey: ENTemporaryExposureKey? { + var temporaryExposureKey: DataManager.TemporaryExposureKey? { guard let keyData = self.infectedKey else { return nil } - return ENTemporaryExposureKey( + return .init( keyData: keyData, rollingStartNumber: ENIntervalNumber(self.rollingStartNumber) ) @@ -237,12 +244,12 @@ extension RemoteInfectedKeyEntity { } extension LocalInfectionKeyEntity { - var temporaryExposureKey: ENTemporaryExposureKey? { + var temporaryExposureKey: DataManager.TemporaryExposureKey? { guard let keyData = self.infectedKey else { return nil } - return ENTemporaryExposureKey( + return .init( keyData: keyData, rollingStartNumber: ENIntervalNumber(self.rollingStartNumber) ) @@ -250,7 +257,7 @@ extension LocalInfectionKeyEntity { } extension DataManager { - func submitReport(formData: InfectedKeysFormData, keys: [ENTemporaryExposureKey], completion: @escaping (Bool, Swift.Error?) -> Void) { + func submitReport(formData: InfectedKeysFormData, keys: [DataManager.TemporaryExposureKey], completion: @escaping (Bool, Swift.Error?) -> Void) { let context = self.persistentContainer.newBackgroundContext() context.perform { diff --git a/TracePrivately/Classes/ENMockFramework.swift b/TracePrivately/Classes/ENMockFramework.swift index f81fb00..36bd587 100644 --- a/TracePrivately/Classes/ENMockFramework.swift +++ b/TracePrivately/Classes/ENMockFramework.swift @@ -595,3 +595,4 @@ extension String { } } } + diff --git a/TracePrivately/Classes/KeyServer/KeyServer.swift b/TracePrivately/Classes/KeyServer/KeyServer.swift index 968b602..98a92b3 100644 --- a/TracePrivately/Classes/KeyServer/KeyServer.swift +++ b/TracePrivately/Classes/KeyServer/KeyServer.swift @@ -191,7 +191,7 @@ extension KeyServer { Refer to `KeyServer.yaml` for expected request and response format. */ - func submitInfectedKeys(formData: InfectedKeysFormData, keys: [ENTemporaryExposureKey], previousSubmissionId: String?, completion: @escaping (Bool, String?, Swift.Error?) -> Void) { + func submitInfectedKeys(formData: InfectedKeysFormData, keys: [DataManager.TemporaryExposureKey], previousSubmissionId: String?, completion: @escaping (Bool, String?, Swift.Error?) -> Void) { self._submitInfectedKeys(formData: formData, keys: keys, previousSubmissionId: previousSubmissionId) { success, submissionId, error in if let error = error as? KeyServer.Error, error.shouldRetryWithAuthRequest { @@ -212,7 +212,7 @@ extension KeyServer { } } - private func _submitInfectedKeys(formData: InfectedKeysFormData, keys: [ENTemporaryExposureKey], previousSubmissionId: String?, completion: @escaping (Bool, String?, Swift.Error?) -> Void) { + private func _submitInfectedKeys(formData: InfectedKeysFormData, keys: [DataManager.TemporaryExposureKey], previousSubmissionId: String?, completion: @escaping (Bool, String?, Swift.Error?) -> Void) { guard let endPoint = self.config.submitInfected else { completion(false, nil, Error.invalidConfig) @@ -310,8 +310,8 @@ extension KeyServer { struct InfectedKeysResponse { let date: Date - let keys: [ENTemporaryExposureKey] - let deletedKeys: [ENTemporaryExposureKey] + let keys: [DataManager.TemporaryExposureKey] + let deletedKeys: [DataManager.TemporaryExposureKey] } func retrieveInfectedKeys(since date: Date?, completion: @escaping (InfectedKeysResponse?, Swift.Error?) -> Void) { @@ -398,7 +398,7 @@ extension KeyServer { return } - let keys: [ENTemporaryExposureKey] = keysData.compactMap { ENTemporaryExposureKey(jsonData: $0) } + let keys: [DataManager.TemporaryExposureKey] = keysData.compactMap { DataManager.TemporaryExposureKey(jsonData: $0) } print("Found \(keys.count) key(s)") @@ -415,7 +415,7 @@ extension KeyServer { } } -extension ENTemporaryExposureKey { +extension DataManager.TemporaryExposureKey { init?(jsonData: [String: Any]) { guard let base64str = jsonData["d"] as? String, let keyData = Data(base64Encoded: base64str) else { return nil @@ -429,7 +429,7 @@ extension ENTemporaryExposureKey { } } -extension ENTemporaryExposureKey { +extension DataManager.TemporaryExposureKey { // TODO: Implement this so data can be read off the wire in binary format // init?(networkData: Data) { // diff --git a/TracePrivately/Classes/View Controllers/SettingsViewController.swift b/TracePrivately/Classes/View Controllers/SettingsViewController.swift index 5878354..bbb250b 100644 --- a/TracePrivately/Classes/View Controllers/SettingsViewController.swift +++ b/TracePrivately/Classes/View Controllers/SettingsViewController.swift @@ -37,6 +37,8 @@ extension SettingsViewController { alert.addAction(UIAlertAction(title: NSLocalizedString("reset_keys.button.title", comment: ""), style: .destructive, handler: { _ in + // TODO: Fix + /* let request = ENSelfExposureResetRequest() request.activateWithCompletion { _ in @@ -50,6 +52,7 @@ extension SettingsViewController { } } } + */ })) self.present(alert, animated: true, completion: nil) diff --git a/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift b/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift index e68fa98..5237f24 100644 --- a/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift +++ b/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift @@ -251,9 +251,6 @@ extension SubmitInfectionViewController { func createFormElement(field: SubmitInfectionConfig.Field) -> SubmitInfectionFormContainerView? { - let isDarkMode = self.isDarkMode - - var headingSubViews: [UIView] = [] var bodySubViews: [UIView] = [] @@ -404,45 +401,39 @@ extension SubmitInfectionViewController { let haptics = UINotificationFeedbackGenerator() haptics.notificationOccurred(.success) - - let request = ENSelfExposureInfoRequest() - request.activateWithCompletion { error in - defer { - request.invalidate() - } - - guard let exposureInfo = request.selfExposureInfo else { - var showError = true - - if let error = error as? ENError { - switch error.errorCode { - case .notAuthorized: - showError = false - default: - break + ContactTraceManager.shared.retrieveSelfDiagnosisKeys { keys, error in + DispatchQueue.main.async { + guard let keys = keys else { + var showError = true + + if let error = error as? ENError { + switch error.errorCode { + case .notAuthorized: + showError = false + default: + break + } + } + + if showError { + self.presentErrorAlert(title: nil, message: error?.localizedDescription ?? NSLocalizedString("infection.report.gathering_data.error", comment: "")) } + + completion(false, error) + return } - if showError { - self.presentErrorAlert(title: nil, message: error?.localizedDescription ?? NSLocalizedString("infection.report.gathering_data.error", comment: "")) - } + let alert = UIAlertController(title: NSLocalizedString("infection.report.submit.title", comment: ""), message: NSLocalizedString("infection.report.submit.message", comment: ""), preferredStyle: .alert) - completion(false, error) - return - } - - let keys = exposureInfo.keys - - let alert = UIAlertController(title: NSLocalizedString("infection.report.submit.title", comment: ""), message: NSLocalizedString("infection.report.submit.message", comment: ""), preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .cancel, handler: nil)) - alert.addAction(UIAlertAction(title: NSLocalizedString("submit", comment: ""), style: .destructive, handler: { action in + alert.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .cancel, handler: nil)) + alert.addAction(UIAlertAction(title: NSLocalizedString("submit", comment: ""), style: .destructive, handler: { action in + + self.submitReport(keys: keys, completion: completion) + })) - self.submitReport(keys: keys, completion: completion) - })) - - self.present(alert, animated: true, completion: nil) + self.present(alert, animated: true, completion: nil) + } } } } @@ -490,7 +481,7 @@ extension SubmitInfectionViewController { } extension SubmitInfectionViewController { - func submitReport(keys: [ENTemporaryExposureKey], completion: @escaping (Bool, Swift.Error?) -> Void) { + func submitReport(keys: [DataManager.TemporaryExposureKey], completion: @escaping (Bool, Swift.Error?) -> Void) { let formData = self.gatherFormData From 59e54ac4f0314d797878a6b775c24d06f58b5c50 Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 07:47:39 +0930 Subject: [PATCH 2/9] Handle new exposure info --- TracePrivately.xcodeproj/project.pbxproj | 4 ++-- .../Classes/ContactTraceManager.swift | 1 + TracePrivately/Classes/DataManager.swift | 20 +++++++++++++++++-- TracePrivately/Classes/ENMockFramework.swift | 1 + .../ExposedViewController.swift | 4 ++-- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/TracePrivately.xcodeproj/project.pbxproj b/TracePrivately.xcodeproj/project.pbxproj index 83229b1..45ffd5a 100644 --- a/TracePrivately.xcodeproj/project.pbxproj +++ b/TracePrivately.xcodeproj/project.pbxproj @@ -633,7 +633,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = CHZS3BHM57; INFOPLIST_FILE = TracePrivately/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -654,7 +654,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = CHZS3BHM57; INFOPLIST_FILE = TracePrivately/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/TracePrivately/Classes/ContactTraceManager.swift b/TracePrivately/Classes/ContactTraceManager.swift index 72a33e4..d5810ff 100644 --- a/TracePrivately/Classes/ContactTraceManager.swift +++ b/TracePrivately/Classes/ContactTraceManager.swift @@ -6,6 +6,7 @@ import Foundation import UserNotifications import UIKit +import ExposureNotification class ContactTraceManager: NSObject { diff --git a/TracePrivately/Classes/DataManager.swift b/TracePrivately/Classes/DataManager.swift index 028ffb2..6376a09 100644 --- a/TracePrivately/Classes/DataManager.swift +++ b/TracePrivately/Classes/DataManager.swift @@ -4,6 +4,7 @@ // import CoreData +import ExposureNotification class DataManager { static let shared = DataManager() @@ -108,6 +109,15 @@ extension DataManager { let rollingStartNumber: ENIntervalNumber // TODO: Support risk level } + + struct ExposureInfo { + let attenuationValue: ENAttenuation + let date: Date + let duration: TimeInterval + let totalRiskScore: ENRiskScore + let transmissionRiskLevel: ENRiskLevel + } + func saveInfectedKeys(keys: [TemporaryExposureKey], completion: @escaping (_ numNewKeys: Int, _ error: Swift.Error?) -> Void) { @@ -463,12 +473,18 @@ extension DataManager { } extension ExposureContactInfoEntity { - var contactInfo: ENExposureInfo? { + var contactInfo: DataManager.ExposureInfo? { guard let timestamp = self.timestamp else { return nil } - return ENExposureInfo(attenuationValue: UInt8(self.attenuationValue), date: timestamp, duration: self.duration) + return .init( + attenuationValue: UInt8(self.attenuationValue), + date: timestamp, + duration: self.duration, + totalRiskScore: .zero, // TODO: Fix + transmissionRiskLevel: .low // TODO: Fix + ) } func matches(exposure: ENExposureInfo) -> Bool { diff --git a/TracePrivately/Classes/ENMockFramework.swift b/TracePrivately/Classes/ENMockFramework.swift index 36bd587..e7dc8b0 100644 --- a/TracePrivately/Classes/ENMockFramework.swift +++ b/TracePrivately/Classes/ENMockFramework.swift @@ -596,3 +596,4 @@ extension String { } } + diff --git a/TracePrivately/Classes/View Controllers/ExposedViewController.swift b/TracePrivately/Classes/View Controllers/ExposedViewController.swift index 20a8334..66fa3b7 100644 --- a/TracePrivately/Classes/View Controllers/ExposedViewController.swift +++ b/TracePrivately/Classes/View Controllers/ExposedViewController.swift @@ -19,7 +19,7 @@ class ExposedViewController: UICollectionViewController { } enum CellType { - case contact(ENExposureInfo) + case contact(DataManager.ExposureInfo) case intro(String) case nextSteps } @@ -70,7 +70,7 @@ class ExposedViewController: UICollectionViewController { entities = [] } - let contacts: [ENExposureInfo] = entities.compactMap { $0.contactInfo } + let contacts: [DataManager.ExposureInfo] = entities.compactMap { $0.contactInfo } if contacts.count == 0 { let title = String(format: NSLocalizedString("exposure.none.message", comment: ""), Disease.current.localizedTitle) From 8958c37db06afc4b7c281632eb8bd43cd7679f69 Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 08:02:53 +0930 Subject: [PATCH 3/9] Compatibilty with enums and callbacks --- .../Classes/ContactTraceManager.swift | 31 +++++++--- TracePrivately/Classes/ENMockFramework.swift | 57 ++++++++++++++----- .../View Controllers/MainViewController.swift | 3 +- .../SubmitInfectionViewController.swift | 3 +- 4 files changed, 69 insertions(+), 25 deletions(-) diff --git a/TracePrivately/Classes/ContactTraceManager.swift b/TracePrivately/Classes/ContactTraceManager.swift index d5810ff..973ebdd 100644 --- a/TracePrivately/Classes/ContactTraceManager.swift +++ b/TracePrivately/Classes/ContactTraceManager.swift @@ -140,7 +140,15 @@ extension ContactTraceManager { fileprivate func addAndFinalizeKeys(session: ENExposureDetectionSession, keys: [DataManager.TemporaryExposureKey], completion: @escaping (Swift.Error?) -> Void) { - let k: [ENTemporaryExposureKey] = keys.map { .init(keyData: $0.keyData, rollingStartNumber: $0.rollingStartNumber) } + let k: [ENTemporaryExposureKey] = keys.map { k in + + let key = ENTemporaryExposureKey() + key.keyData = k.keyData + key.rollingStartNumber = k.rollingStartNumber + key.transmissionRiskLevel = .high // TODO: Use correct value + + return key + } session.batchAddDiagnosisKeys(inKeys: k) { error in session.finishedDiagnosisKeysWithCompletion { summary, error in @@ -332,7 +340,7 @@ extension ContactTraceManager { let settings = ENSettings(enableState: true) let request = ENSettingsChangeRequest(settings: settings) - request.activateWithCompletion { error in + request.activate { error in defer { request.invalidate() } @@ -349,7 +357,7 @@ extension ContactTraceManager { if error != nil { let settings = ENSettings(enableState: false) let request = ENSettingsChangeRequest(settings: settings) - request.activateWithCompletion { _ in + request.activate { _ in defer { request.invalidate() } @@ -383,7 +391,7 @@ extension ContactTraceManager { let settings = ENSettings(enableState: false) let request = ENSettingsChangeRequest(settings: settings) - request.activateWithCompletion { _ in + request.activate { _ in defer { request.invalidate() } @@ -399,14 +407,21 @@ extension ContactTraceManager { let dispatchGroup = DispatchGroup() let session = ENExposureDetectionSession() + + let configuration = ENExposureConfiguration() + + // TODO: Handle the configuration correctly + /* session.attenuationThreshold = self.config.session.attenuationThreshold session.durationThreshold = self.config.session.durationThreshold + */ + session.configuration = configuration var sessionError: Swift.Error? dispatchGroup.enter() - session.activateWithCompletion { error in + session.activate { error in if let error = error { sessionError = error @@ -472,12 +487,12 @@ extension ENExposureDetectionSession { return } - guard maxKeyCount > 0 else { + guard maximumKeyCount > 0 else { completion(nil) return } - let cursor = keys.index(keys.startIndex, offsetBy: maxKeyCount, limitedBy: keys.endIndex) ?? keys.endIndex + let cursor = keys.index(keys.startIndex, offsetBy: maximumKeyCount, limitedBy: keys.endIndex) ?? keys.endIndex let batch = Array(keys.prefix(upTo: cursor)) let remaining = Array(keys.suffix(from: cursor)) @@ -499,7 +514,7 @@ extension ContactTraceManager { func retrieveSelfDiagnosisKeys(completion: @escaping ([DataManager.TemporaryExposureKey]?, Swift.Error?) -> Void) { let request = ENSelfExposureInfoRequest() - request.activateWithCompletion { error in + request.activate { error in defer { request.invalidate() } diff --git a/TracePrivately/Classes/ENMockFramework.swift b/TracePrivately/Classes/ENMockFramework.swift index e7dc8b0..bf546b3 100644 --- a/TracePrivately/Classes/ENMockFramework.swift +++ b/TracePrivately/Classes/ENMockFramework.swift @@ -41,10 +41,10 @@ enum ENErrorCode { } struct ENError: LocalizedError { - let errorCode: ENErrorCode + let code: ENErrorCode var localizedDescription: String { - return errorCode.localizedTitle + return code.localizedTitle } } @@ -67,7 +67,7 @@ protocol ENActivatable { var dispatchQueue: DispatchQueue? { get set } var invalidationHandler: (() -> Void)? { get set } - func activateWithCompletion(_ completion: @escaping ENErrorHandler) + func activate(_ completion: @escaping ENErrorHandler) func invalidate() } @@ -140,9 +140,26 @@ class ENSettingsChangeRequest: ENAuthorizableBaseRequest { typealias ENIntervalNumber = UInt32 -struct ENTemporaryExposureKey { - let keyData: Data - let rollingStartNumber: ENIntervalNumber +enum ENRiskLevel { + case invalid + case lowest + case low + case lowMedium + case medium + case mediumHigh + case high + case veryHigh + case highest +} + +class ENTemporaryExposureKey { + var keyData: Data! + var rollingStartNumber: ENIntervalNumber! + var transmissionRiskLevel: ENRiskLevel! + + init() { + + } } extension ENTemporaryExposureKey { @@ -190,10 +207,13 @@ typealias ENExposureDetectionFinishCompletion = ((ENExposureDetectionSummary?, S typealias ENExposureDetectionGetExposureInfoCompletion = (([ENExposureInfo]?, Bool, Swift.Error?) -> Void) +struct ENExposureConfiguration { + +} + class ENExposureDetectionSession: ENBaseRequest { - var attenuationThreshold: UInt8 = 0 - var durationThreshold: TimeInterval = 0 - var maxKeyCount: Int = 10 + var configuration = ENExposureConfiguration() + var maximumKeyCount: Int = 10 private var _infectedKeys: [ENTemporaryExposureKey] = [] @@ -251,7 +271,7 @@ class ENExposureDetectionSession: ENBaseRequest { queue.asyncAfter(deadline: .now() + delay) { guard !self.isInvalidated else { self.cursor = 0 - completion(nil, true, ENError(errorCode: .invalidated)) + completion(nil, true, ENError(code: .invalidated)) return } @@ -379,7 +399,7 @@ class ENAuthorizableBaseRequest: ENBaseRequest, ENAuthorizable { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Deny", style: .cancel, handler: { action in - completion(ENError(errorCode: .notAuthorized)) + completion(ENError(code: .notAuthorized)) return })) @@ -388,7 +408,7 @@ class ENAuthorizableBaseRequest: ENBaseRequest, ENAuthorizable { })) guard let vc = UIApplication.shared.windows.first?.rootViewController else { - completion(ENError(errorCode: .unknown)) + completion(ENError(code: .unknown)) return } @@ -463,14 +483,14 @@ class ENBaseRequest: ENActivatable { } } - final func activateWithCompletion(_ completion: @escaping (Swift.Error?) -> Void) { + final func activate(_ completion: @escaping (Swift.Error?) -> Void) { let queue: DispatchQueue = self.dispatchQueue ?? .main self.isRunning = true self.activate(queue: queue) { error in guard !self.isInvalidated else { - completion(ENError(errorCode: .invalidated)) + completion(ENError(code: .invalidated)) return } @@ -567,7 +587,14 @@ private class ENInternalState { let intervalNumber = ENIntervalNumber(date.timeIntervalSince1970 / 600) let rollingStartNumber = intervalNumber / 144 * 144 - keys.append(ENTemporaryExposureKey(keyData: keyData, rollingStartNumber: rollingStartNumber)) + + + let key = ENTemporaryExposureKey() + key.keyData = keyData + key.rollingStartNumber = rollingStartNumber + key.transmissionRiskLevel = .high // TODO: Make better use of risk level + + keys.append(key) } print("Generated keys: \(keys)") diff --git a/TracePrivately/Classes/View Controllers/MainViewController.swift b/TracePrivately/Classes/View Controllers/MainViewController.swift index 9c2d75f..1492575 100644 --- a/TracePrivately/Classes/View Controllers/MainViewController.swift +++ b/TracePrivately/Classes/View Controllers/MainViewController.swift @@ -4,6 +4,7 @@ // import UIKit +import ExposureNotification class MainViewController: UIViewController { @@ -281,7 +282,7 @@ extension MainViewController { ContactTraceManager.shared.startTracing { error in if let error = error { - if let error = error as? ENError, error.errorCode == .notAuthorized { + if let error = error as? ENError, error.code == .notAuthorized { } else { diff --git a/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift b/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift index 5237f24..f40bb08 100644 --- a/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift +++ b/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift @@ -4,6 +4,7 @@ // import UIKit +import ExposureNotification class SubmitInfectionViewController: UIViewController { @@ -408,7 +409,7 @@ extension SubmitInfectionViewController { var showError = true if let error = error as? ENError { - switch error.errorCode { + switch error.code { case .notAuthorized: showError = false default: From 5ea442d7b10b6fd2d9ef6264c84a5a7b3768e1bd Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 08:10:06 +0930 Subject: [PATCH 4/9] Compatibilty with enums and callbacks --- .../Classes/ContactTraceManager.swift | 21 +++--- TracePrivately/Classes/ENMockFramework.swift | 65 +++++++++---------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/TracePrivately/Classes/ContactTraceManager.swift b/TracePrivately/Classes/ContactTraceManager.swift index 973ebdd..f847d86 100644 --- a/TracePrivately/Classes/ContactTraceManager.swift +++ b/TracePrivately/Classes/ContactTraceManager.swift @@ -150,8 +150,8 @@ extension ContactTraceManager { return key } - session.batchAddDiagnosisKeys(inKeys: k) { error in - session.finishedDiagnosisKeysWithCompletion { summary, error in + session.batchAddDiagnosisKeys(k) { error in + session.finishedDiagnosisKeys { summary, error in guard let summary = summary else { completion(error) return @@ -166,9 +166,9 @@ extension ContactTraceManager { } // Documentation says use a reasonable number, such as 100 - let maxCount: UInt32 = 100 + let maximumCount: Int = 100 - self.getExposures(session: session, maxCount: maxCount, exposures: []) { exposures, error in + self.getExposures(session: session, maximumCount: maximumCount, exposures: []) { exposures, error in guard let exposures = exposures else { completion(error) return @@ -190,8 +190,9 @@ extension ContactTraceManager { } // Recursively retrieves exposures until all are received - private func getExposures(session: ENExposureDetectionSession, maxCount: UInt32, exposures: [ENExposureInfo], completion: @escaping ([ENExposureInfo]?, Swift.Error?) -> Void) { - session.getExposureInfoWithMaxCount(maxCount: maxCount) { newExposures, inDone, error in + private func getExposures(session: ENExposureDetectionSession, maximumCount: Int, exposures: [ENExposureInfo], completion: @escaping ([ENExposureInfo]?, Swift.Error?) -> Void) { + + session.getExposureInfo(withMaximumCount: maximumCount) { newExposures, inDone, error in if let error = error { completion(exposures, error) @@ -204,7 +205,7 @@ extension ContactTraceManager { completion(allExposures, nil) } else { - self.getExposures(session: session, maxCount: maxCount, exposures: allExposures, completion: completion) + self.getExposures(session: session, maximumCount: maximumCount, exposures: allExposures, completion: completion) } } } @@ -480,7 +481,7 @@ extension ContactTraceManager: UNUserNotificationCenterDelegate { extension ENExposureDetectionSession { // Modified from https://gist.github.com/mattt/17c880d64c362b923e13c765f5b1c75a - func batchAddDiagnosisKeys(inKeys keys: [ENTemporaryExposureKey], completion: @escaping ENErrorHandler) { + func batchAddDiagnosisKeys(_ keys: [ENTemporaryExposureKey], completion: @escaping ENErrorHandler) { guard !keys.isEmpty else { completion(nil) @@ -499,11 +500,11 @@ extension ENExposureDetectionSession { print("Adding: \(batch)") // withoutActuallyEscaping(completion) { escapingCompletion in - addDiagnosisKeys(inKeys: batch) { error in + addDiagnosisKeys(batch) { error in if let error = error { completion(error) } else { - self.batchAddDiagnosisKeys(inKeys: remaining, completion: completion) + self.batchAddDiagnosisKeys(remaining, completion: completion) } } // } diff --git a/TracePrivately/Classes/ENMockFramework.swift b/TracePrivately/Classes/ENMockFramework.swift index bf546b3..b94af2a 100644 --- a/TracePrivately/Classes/ENMockFramework.swift +++ b/TracePrivately/Classes/ENMockFramework.swift @@ -67,7 +67,7 @@ protocol ENActivatable { var dispatchQueue: DispatchQueue? { get set } var invalidationHandler: (() -> Void)? { get set } - func activate(_ completion: @escaping ENErrorHandler) + func activate(_ completionHandler: @escaping ENErrorHandler) func invalidate() } @@ -107,10 +107,10 @@ class ENSettingsGetRequest: ENBaseRequest { } - override fileprivate func activate(queue: DispatchQueue, completion: @escaping (Error?) -> Void) { + override fileprivate func activate(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { queue.async { self.settings = ENSettings(enableState: ENInternalState.shared.tracingEnabled) - completion(nil) + completionHandler(nil) } } } @@ -130,10 +130,10 @@ class ENSettingsChangeRequest: ENAuthorizableBaseRequest { return self.settings.enableState == true } - override fileprivate func activateWithPermission(queue: DispatchQueue, completion: @escaping (Error?) -> Void) { + override fileprivate func activateWithPermission(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { queue.async { ENInternalState.shared.tracingEnabled = self.settings.enableState - completion(nil) + completionHandler(nil) } } } @@ -205,7 +205,7 @@ struct ENExposureDetectionSummary { typealias ENExposureDetectionFinishCompletion = ((ENExposureDetectionSummary?, Swift.Error?) -> Void) -typealias ENExposureDetectionGetExposureInfoCompletion = (([ENExposureInfo]?, Bool, Swift.Error?) -> Void) +typealias ENGetExposureInfoCompletion = (([ENExposureInfo]?, Bool, Swift.Error?) -> Void) struct ENExposureConfiguration { @@ -229,7 +229,7 @@ class ENExposureDetectionSession: ENBaseRequest { } } - func addDiagnosisKeys(inKeys keys: [ENTemporaryExposureKey], completion: @escaping ENErrorHandler) { + func addDiagnosisKeys(_ keys: [ENTemporaryExposureKey], completionHandler: @escaping ENErrorHandler) { enQueue.sync { self._infectedKeys.append(contentsOf: keys) } @@ -237,11 +237,11 @@ class ENExposureDetectionSession: ENBaseRequest { let queue = self.dispatchQueue ?? .main queue.asyncAfter(deadline: .now() + 0.5) { - completion(nil) + completionHandler(nil) } } - func finishedDiagnosisKeysWithCompletion(completion: @escaping ENExposureDetectionFinishCompletion) { + func finishedDiagnosisKeys(completionHandler: @escaping ENExposureDetectionFinishCompletion) { let delay: TimeInterval = 0.5 @@ -255,14 +255,14 @@ class ENExposureDetectionSession: ENBaseRequest { matchedKeyCount: UInt64(min(Self.maximumFakeMatches, keys.count)) ) - completion(summary, nil) + completionHandler(summary, nil) } } private var cursor: Int = 0 - func getExposureInfoWithMaxCount(maxCount: UInt32, completion: @escaping ENExposureDetectionGetExposureInfoCompletion) { + func getExposureInfo(withMaximumCount maximumCount: Int, completionHandler: @escaping ENGetExposureInfoCompletion) { let queue: DispatchQueue = self.dispatchQueue ?? .main @@ -271,7 +271,7 @@ class ENExposureDetectionSession: ENBaseRequest { queue.asyncAfter(deadline: .now() + delay) { guard !self.isInvalidated else { self.cursor = 0 - completion(nil, true, ENError(code: .invalidated)) + completionHandler(nil, true, ENError(code: .invalidated)) return } @@ -279,18 +279,18 @@ class ENExposureDetectionSession: ENBaseRequest { let allKeys: [ENTemporaryExposureKey] = enQueue.sync { self.remoteInfectedKeys } guard allKeys.count > 0 else { - completion([], true, nil) + completionHandler([], true, nil) return } let allMatchedKeys: [ENTemporaryExposureKey] = Array(allKeys[0 ..< min(Self.maximumFakeMatches, allKeys.count)]) let fromIndex = self.cursor - let toIndex = min(allMatchedKeys.count, self.cursor + Int(maxCount)) + let toIndex = min(allMatchedKeys.count, self.cursor + Int(maximumCount)) guard fromIndex < toIndex else { self.cursor = 0 - completion([], true, nil) + completionHandler([], true, nil) return } @@ -307,7 +307,7 @@ class ENExposureDetectionSession: ENBaseRequest { let inDone = toIndex >= allMatchedKeys.count self.cursor = inDone ? 0 : toIndex - completion(contacts, inDone, nil) + completionHandler(contacts, inDone, nil) } } } @@ -342,7 +342,7 @@ class ENSelfExposureInfoRequest: ENAuthorizableBaseRequest { return "Allow this app to retrieve your anonymous tracing keys?" } - override fileprivate func activateWithPermission(queue: DispatchQueue, completion: @escaping (Error?) -> Void) { + override fileprivate func activateWithPermission(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { let delay: TimeInterval = 0.5 @@ -351,7 +351,7 @@ class ENSelfExposureInfoRequest: ENAuthorizableBaseRequest { let info = ENSelfExposureInfo(keys: ENInternalState.shared.dailyKeys) self.selfExposureInfo = info - completion(nil) + completionHandler(nil) } } } @@ -362,14 +362,14 @@ class ENSelfExposureResetRequest: ENAuthorizableBaseRequest { return "Allow this app to reset your anonymous tracing keys?" } - override fileprivate func activateWithPermission(queue: DispatchQueue, completion: @escaping (Error?) -> Void) { + override fileprivate func activateWithPermission(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { print("Resetting keys ...") queue.asyncAfter(deadline: .now() + 0.5) { print("Finished resetting keys") // Nothing to do since we're generating fake stable keys for the purpose of testing - completion(nil) + completionHandler(nil) } } } @@ -389,7 +389,7 @@ class ENAuthorizableBaseRequest: ENBaseRequest, ENAuthorizable { return true } - final override fileprivate func activate(queue: DispatchQueue, completion: @escaping (Error?) -> Void) { + final override fileprivate func activate(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { if self.shouldPrompt { DispatchQueue.main.async { @@ -399,16 +399,16 @@ class ENAuthorizableBaseRequest: ENBaseRequest, ENAuthorizable { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Deny", style: .cancel, handler: { action in - completion(ENError(code: .notAuthorized)) + completionHandler(ENError(code: .notAuthorized)) return })) alert.addAction(UIAlertAction(title: "Allow", style: .default, handler: { action in - self.activateWithPermission(queue: queue, completion: completion) + self.activateWithPermission(queue: queue, completionHandler: completionHandler) })) guard let vc = UIApplication.shared.windows.first?.rootViewController else { - completion(ENError(code: .unknown)) + completionHandler(ENError(code: .unknown)) return } @@ -422,14 +422,14 @@ class ENAuthorizableBaseRequest: ENBaseRequest, ENAuthorizable { } else { let queue = self.dispatchQueue ?? .main - self.activateWithPermission(queue: queue, completion: completion) + self.activateWithPermission(queue: queue, completionHandler: completionHandler) } } - fileprivate func activateWithPermission(queue: DispatchQueue, completion: @escaping (Error?) -> Void) { + fileprivate func activateWithPermission(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { queue.async { print("Should be overridden") - completion(nil) + completionHandler(nil) } } } @@ -483,26 +483,26 @@ class ENBaseRequest: ENActivatable { } } - final func activate(_ completion: @escaping (Swift.Error?) -> Void) { + final func activate(_ completionHandler: @escaping (Swift.Error?) -> Void) { let queue: DispatchQueue = self.dispatchQueue ?? .main self.isRunning = true self.activate(queue: queue) { error in guard !self.isInvalidated else { - completion(ENError(code: .invalidated)) + completionHandler(ENError(code: .invalidated)) return } self.isRunning = false - completion(error) + completionHandler(error) } } - fileprivate func activate(queue: DispatchQueue, completion: @escaping (Swift.Error?) -> Void) { + fileprivate func activate(queue: DispatchQueue, completionHandler: @escaping (Swift.Error?) -> Void) { queue.async { print("Should be overridden") - completion(nil) + completionHandler(nil) } } @@ -623,4 +623,3 @@ extension String { } } - From c773f9bd83659594572ddd39d3a937a610ab9c29 Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 08:28:31 +0930 Subject: [PATCH 5/9] API alignment --- .../Classes/ContactTraceManager.swift | 107 ++++++++------ TracePrivately/Classes/ENMockFramework.swift | 133 +++++------------- .../SettingsViewController.swift | 11 +- 3 files changed, 95 insertions(+), 156 deletions(-) diff --git a/TracePrivately/Classes/ContactTraceManager.swift b/TracePrivately/Classes/ContactTraceManager.swift index f847d86..89f1d40 100644 --- a/TracePrivately/Classes/ContactTraceManager.swift +++ b/TracePrivately/Classes/ContactTraceManager.swift @@ -22,7 +22,8 @@ class ContactTraceManager: NSObject { static let backgroundProcessingTaskIdentifier = "ctm.processor" - fileprivate var exposureDetectionSession: ENExposureDetectionSession? + fileprivate var enManager: ENManager? + fileprivate var enDetectionSession: ENExposureDetectionSession? private var _isUpdatingEnabledState = false @objc dynamic var isUpdatingEnabledState: Bool { @@ -124,7 +125,7 @@ extension ContactTraceManager { self.saveNewInfectedKeys(keys: response.keys) { numNewKeys, error in self.saveLastReceivedInfectedKeys(date: response.date) - guard let session = self.exposureDetectionSession else { + guard let session = self.enDetectionSession else { self.isUpdatingExposures = false completion(nil) return @@ -338,43 +339,48 @@ extension ContactTraceManager { self.isUpdatingEnabledState = true - let settings = ENSettings(enableState: true) + self.enManager?.invalidate() - let request = ENSettingsChangeRequest(settings: settings) - request.activate { error in - defer { - request.invalidate() - } + let manager = ENManager() + self.enManager = manager + + manager.activate { error in if let error = error { + manager.invalidate() + self.isUpdatingEnabledState = false self.isContactTracingEnabled = false completion(error) return } - - self.startExposureChecking { error in - - if error != nil { - let settings = ENSettings(enableState: false) - let request = ENSettingsChangeRequest(settings: settings) - request.activate { _ in - defer { - request.invalidate() - } + + manager.setExposureNotificationEnabled(true) { error in + if let error = error { + manager.invalidate() + + self.isUpdatingEnabledState = false + self.isContactTracingEnabled = false + completion(error) + return + } + + self.startExposureChecking { error in + + if error != nil { + manager.invalidate() self.isUpdatingEnabledState = false self.isContactTracingEnabled = false completion(error) + return } - - return + + self.isContactTracingEnabled = true + self.isUpdatingEnabledState = false + + completion(error) } - - self.isContactTracingEnabled = true - self.isUpdatingEnabledState = false - - completion(error) } } } @@ -387,19 +393,14 @@ extension ContactTraceManager { self.isUpdatingEnabledState = true self.stopExposureChecking() - self.exposureDetectionSession?.invalidate() - self.exposureDetectionSession = nil - - let settings = ENSettings(enableState: false) - let request = ENSettingsChangeRequest(settings: settings) - request.activate { _ in - defer { - request.invalidate() - } + self.enDetectionSession?.invalidate() + self.enDetectionSession = nil - self.isContactTracingEnabled = false - self.isUpdatingEnabledState = false - } + self.enManager?.invalidate() + self.enManager = nil + + self.isContactTracingEnabled = false + self.isUpdatingEnabledState = false } } @@ -457,7 +458,7 @@ extension ContactTraceManager { } } - self.exposureDetectionSession = session + self.enDetectionSession = session dispatchGroup.notify(queue: .main) { let error = sessionError @@ -466,7 +467,7 @@ extension ContactTraceManager { } fileprivate func stopExposureChecking() { - self.exposureDetectionSession = nil + self.enDetectionSession = nil } } @@ -513,14 +514,15 @@ extension ENExposureDetectionSession { extension ContactTraceManager { func retrieveSelfDiagnosisKeys(completion: @escaping ([DataManager.TemporaryExposureKey]?, Swift.Error?) -> Void) { - let request = ENSelfExposureInfoRequest() - request.activate { error in - defer { - request.invalidate() - } - - guard let keys = request.selfExposureInfo?.keys else { + guard let manager = self.enManager else { + // TODO: Handle this error better + completion(nil, nil) + return + } + + manager.getDiagnosisKeys { keys, error in + guard let keys = keys else { completion(nil, error) return } @@ -531,3 +533,16 @@ extension ContactTraceManager { } } } + +extension ContactTraceManager { + func resetAllData(completion: @escaping (Swift.Error?) -> Void) { + guard let manager = self.enManager else { + completion(nil) + return + } + + manager.resetAllData { error in + completion(error) + } + } +} diff --git a/TracePrivately/Classes/ENMockFramework.swift b/TracePrivately/Classes/ENMockFramework.swift index b94af2a..af6eaf5 100644 --- a/TracePrivately/Classes/ENMockFramework.swift +++ b/TracePrivately/Classes/ENMockFramework.swift @@ -6,6 +6,7 @@ import Foundation import UIKit +/* enum ENErrorCode { case success case unknown @@ -76,68 +77,6 @@ protocol ENAuthorizable { var authorizationMode: ENAuthorizationMode { get set } } -typealias ENMultiState = Bool - -class ENSettings { - let enableState: ENMultiState - - init(enableState: ENMultiState) { - self.enableState = enableState - } -} - -class ENMutableSettings: ENSettings { - -} - -class ENSettingsGetRequest: ENBaseRequest { - private var _settings: ENSettings? = nil - - var settings: ENSettings? { - get { - return enQueue.sync { - return self._settings - } - } - set { - enQueue.sync { - self._settings = newValue - } - } - - } - - override fileprivate func activate(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { - queue.async { - self.settings = ENSettings(enableState: ENInternalState.shared.tracingEnabled) - completionHandler(nil) - } - } -} - -class ENSettingsChangeRequest: ENAuthorizableBaseRequest { - let settings: ENSettings - - override var permissionDialogMessage: String? { - return "Allow this app to detect exposures?" - } - - init(settings: ENSettings) { - self.settings = settings - } - - override var shouldPrompt: Bool { - return self.settings.enableState == true - } - - override fileprivate func activateWithPermission(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { - queue.async { - ENInternalState.shared.tracingEnabled = self.settings.enableState - completionHandler(nil) - } - } -} - typealias ENIntervalNumber = UInt32 enum ENRiskLevel { @@ -198,6 +137,37 @@ extension ENTemporaryExposureKey { } } +typealias ENGetDiagnosisKeysHandler = ([ENTemporaryExposureKey]?, Error?) -> Void + +class ENManager: ENBaseRequest { + + func setExposureNotificationEnabled(_ flag: Bool, completion: @escaping ENErrorHandler) { + // TODO: Doesn't do anything + // TODO: Request permission here + completion(nil) + } + +// override var permissionDialogMessage: String? { +// return "Allow this app to retrieve your anonymous tracing keys?" +// } + + func getDiagnosisKeys(completionHandler: @escaping ENGetDiagnosisKeysHandler) { + + let delay: TimeInterval = 0.5 + + let queue = self.dispatchQueue ?? .main + + queue.asyncAfter(deadline: .now() + delay) { + completionHandler(ENInternalState.shared.dailyKeys, nil) + } + } + + func resetAllData(completionHandler: @escaping ENErrorHandler) { + // TODO: Show auth dialog here + completionHandler(nil) + } +} + struct ENExposureDetectionSummary { let daysSinceLastExposure: Int let matchedKeyCount: UInt64 @@ -318,44 +288,6 @@ struct ENExposureInfo { let duration: TimeInterval } -struct ENSelfExposureInfo { - let keys: [ENTemporaryExposureKey] -} - -class ENSelfExposureInfoRequest: ENAuthorizableBaseRequest { - private var _selfExposureInfo: ENSelfExposureInfo? - - var selfExposureInfo: ENSelfExposureInfo? { - get { - return enQueue.sync { - return self._selfExposureInfo - } - } - set { - enQueue.sync { - self._selfExposureInfo = newValue - } - } - } - - override var permissionDialogMessage: String? { - return "Allow this app to retrieve your anonymous tracing keys?" - } - - override fileprivate func activateWithPermission(queue: DispatchQueue, completionHandler: @escaping (Error?) -> Void) { - - let delay: TimeInterval = 0.5 - - queue.asyncAfter(deadline: .now() + delay) { - - let info = ENSelfExposureInfo(keys: ENInternalState.shared.dailyKeys) - self.selfExposureInfo = info - - completionHandler(nil) - } - } -} - class ENSelfExposureResetRequest: ENAuthorizableBaseRequest { override var permissionDialogMessage: String? { @@ -623,3 +555,4 @@ extension String { } } +*/ diff --git a/TracePrivately/Classes/View Controllers/SettingsViewController.swift b/TracePrivately/Classes/View Controllers/SettingsViewController.swift index bbb250b..7b36e1c 100644 --- a/TracePrivately/Classes/View Controllers/SettingsViewController.swift +++ b/TracePrivately/Classes/View Controllers/SettingsViewController.swift @@ -37,22 +37,13 @@ extension SettingsViewController { alert.addAction(UIAlertAction(title: NSLocalizedString("reset_keys.button.title", comment: ""), style: .destructive, handler: { _ in - // TODO: Fix - /* - let request = ENSelfExposureResetRequest() - - request.activateWithCompletion { _ in - defer { - request.invalidate() - } - + ContactTraceManager.shared.resetAllData { _ in DataManager.shared.deleteLocalInfections { _ in DispatchQueue.main.async { self.dismiss(animated: true, completion: nil) } } } - */ })) self.present(alert, animated: true, completion: nil) From 3fc3c1e1c7af8adf27a987f90c5c42faff9693bf Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 08:46:11 +0930 Subject: [PATCH 6/9] Update entitlements --- TracePrivately.xcodeproj/project.pbxproj | 8 ++++++-- TracePrivately/TracePrivately.entitlements | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 TracePrivately/TracePrivately.entitlements diff --git a/TracePrivately.xcodeproj/project.pbxproj b/TracePrivately.xcodeproj/project.pbxproj index 45ffd5a..24304a4 100644 --- a/TracePrivately.xcodeproj/project.pbxproj +++ b/TracePrivately.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ 662105782452602D0011EB42 /* hi-IN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "hi-IN"; path = "hi-IN.lproj/Localizable.strings"; sourceTree = ""; }; 6625219B2453955F00C68AD1 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 6625219C2453957100C68AD1 /* sr-RS */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sr-RS"; path = "sr-RS.lproj/Localizable.strings"; sourceTree = ""; }; + 663BD8B7245A3F8100A3E029 /* TracePrivately.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TracePrivately.entitlements; sourceTree = ""; }; 66437F75244A86AE000B8C6C /* Disease.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disease.swift; sourceTree = ""; }; 66437F9D244A9A02000B8C6C /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; 66437F9F244A9B2C000B8C6C /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; @@ -244,6 +245,7 @@ 66F818692448FD5C0043AC2D /* TracePrivately */ = { isa = PBXGroup; children = ( + 663BD8B7245A3F8100A3E029 /* TracePrivately.entitlements */, 39BD8847244C6CF000EB8B6C /* Storyboards */, 66437F77244A8A4C000B8C6C /* Classes */, 66F818732448FD5E0043AC2D /* Assets.xcassets */, @@ -630,8 +632,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = TracePrivately/TracePrivately.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = CHZS3BHM57; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = TracePrivately/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = ( @@ -651,8 +654,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = TracePrivately/TracePrivately.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = CHZS3BHM57; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = TracePrivately/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/TracePrivately/TracePrivately.entitlements b/TracePrivately/TracePrivately.entitlements new file mode 100644 index 0000000..c97806a --- /dev/null +++ b/TracePrivately/TracePrivately.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.exposure-notification + + + From 4d4bb93160d43e79f508c31d029e18eefb724203 Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 10:24:32 +0930 Subject: [PATCH 7/9] Add support for new risk data in network calls and in database --- TracePrivately.xcodeproj/project.pbxproj | 12 +- .../Classes/ContactTraceManager.swift | 45 +++--- TracePrivately/Classes/DataManager.swift | 54 +++---- TracePrivately/Classes/ENMockFramework.swift | 143 +++++++++++++++--- .../Classes/ExposureDataTypes.swift | 55 +++++++ .../Classes/KeyServer/KeyServer.swift | 33 ++-- .../ExposedViewController.swift | 4 +- .../View Controllers/MainViewController.swift | 21 ++- .../SubmitInfectionViewController.swift | 18 +-- TracePrivately/TracePrivately.entitlements | 3 +- .../.xccurrentversion | 2 +- .../Model 8.xcdatamodel/contents | 39 +++++ 12 files changed, 314 insertions(+), 115 deletions(-) create mode 100644 TracePrivately/Classes/ExposureDataTypes.swift create mode 100644 TracePrivately/TracePrivately.xcdatamodeld/Model 8.xcdatamodel/contents diff --git a/TracePrivately.xcodeproj/project.pbxproj b/TracePrivately.xcodeproj/project.pbxproj index 24304a4..83af750 100644 --- a/TracePrivately.xcodeproj/project.pbxproj +++ b/TracePrivately.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 6614BB052452B9D900885F23 /* ExposureDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6614BB042452B9D900885F23 /* ExposureDetailsViewController.swift */; }; 6621056D245251B10011EB42 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6621056C245251B10011EB42 /* main.swift */; }; 6621057124525FC90011EB42 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6621057324525FC90011EB42 /* Localizable.strings */; }; + 663BD8B9245A510C00A3E029 /* ExposureDataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663BD8B8245A510C00A3E029 /* ExposureDataTypes.swift */; }; 66437F76244A86AE000B8C6C /* Disease.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66437F75244A86AE000B8C6C /* Disease.swift */; }; 66437F9E244A9A02000B8C6C /* TracePrivately.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 66437F9C244A9A02000B8C6C /* TracePrivately.xcdatamodeld */; }; 66437FA0244A9B2C000B8C6C /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66437F9F244A9B2C000B8C6C /* DataManager.swift */; }; @@ -94,6 +95,8 @@ 6625219B2453955F00C68AD1 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 6625219C2453957100C68AD1 /* sr-RS */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sr-RS"; path = "sr-RS.lproj/Localizable.strings"; sourceTree = ""; }; 663BD8B7245A3F8100A3E029 /* TracePrivately.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TracePrivately.entitlements; sourceTree = ""; }; + 663BD8B8245A510C00A3E029 /* ExposureDataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExposureDataTypes.swift; sourceTree = ""; }; + 663BD8BA245A537900A3E029 /* Model 8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 8.xcdatamodel"; sourceTree = ""; }; 66437F75244A86AE000B8C6C /* Disease.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disease.swift; sourceTree = ""; }; 66437F9D244A9A02000B8C6C /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; 66437F9F244A9B2C000B8C6C /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; @@ -189,6 +192,7 @@ 66CB7BD02453D51A0044D32B /* ENMockFramework.swift */, 66437F75244A86AE000B8C6C /* Disease.swift */, 66437F9F244A9B2C000B8C6C /* DataManager.swift */, + 663BD8B8245A510C00A3E029 /* ExposureDataTypes.swift */, 668CE842244D01F50065960A /* ContactTraceManager.swift */, 66C4BBC424564B56008AACC2 /* ExposureNotificationConfig.swift */, 66CB95BC244FB51000D775B6 /* ActionButton.swift */, @@ -388,6 +392,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 663BD8B9245A510C00A3E029 /* ExposureDataTypes.swift in Sources */, 66793892245460B600868B84 /* KeyServer.swift in Sources */, 66848C2D244D5F8800497D2A /* AsyncBlockOperation.swift in Sources */, 66EAEB62244DA72E0044FF70 /* SubmitInfectionViewController.swift in Sources */, @@ -634,7 +639,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = TracePrivately/TracePrivately.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = CHZS3BHM57; INFOPLIST_FILE = TracePrivately/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = ( @@ -656,7 +661,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = TracePrivately/TracePrivately.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = CHZS3BHM57; INFOPLIST_FILE = TracePrivately/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = ( @@ -706,6 +711,7 @@ 66437F9C244A9A02000B8C6C /* TracePrivately.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 663BD8BA245A537900A3E029 /* Model 8.xcdatamodel */, 664C5236245802C9003EBE63 /* Model 7.xcdatamodel */, 66BF646624578E2B00492070 /* Model 6.xcdatamodel */, 66CB7BD22453EB190044D32B /* Model 5.xcdatamodel */, @@ -714,7 +720,7 @@ 668D4B22244D705100D90C26 /* Model 2.xcdatamodel */, 66437F9D244A9A02000B8C6C /* Model.xcdatamodel */, ); - currentVersion = 664C5236245802C9003EBE63 /* Model 7.xcdatamodel */; + currentVersion = 663BD8BA245A537900A3E029 /* Model 8.xcdatamodel */; path = TracePrivately.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/TracePrivately/Classes/ContactTraceManager.swift b/TracePrivately/Classes/ContactTraceManager.swift index 89f1d40..f1db15a 100644 --- a/TracePrivately/Classes/ContactTraceManager.swift +++ b/TracePrivately/Classes/ContactTraceManager.swift @@ -139,17 +139,9 @@ extension ContactTraceManager { } } - fileprivate func addAndFinalizeKeys(session: ENExposureDetectionSession, keys: [DataManager.TemporaryExposureKey], completion: @escaping (Swift.Error?) -> Void) { + fileprivate func addAndFinalizeKeys(session: ENExposureDetectionSession, keys: [TPTemporaryExposureKey], completion: @escaping (Swift.Error?) -> Void) { - let k: [ENTemporaryExposureKey] = keys.map { k in - - let key = ENTemporaryExposureKey() - key.keyData = k.keyData - key.rollingStartNumber = k.rollingStartNumber - key.transmissionRiskLevel = .high // TODO: Use correct value - - return key - } + let k: [ENTemporaryExposureKey] = keys.map { $0.enExposureKey } session.batchAddDiagnosisKeys(k) { error in session.finishedDiagnosisKeys { summary, error in @@ -191,16 +183,16 @@ extension ContactTraceManager { } // Recursively retrieves exposures until all are received - private func getExposures(session: ENExposureDetectionSession, maximumCount: Int, exposures: [ENExposureInfo], completion: @escaping ([ENExposureInfo]?, Swift.Error?) -> Void) { + private func getExposures(session: ENExposureDetectionSession, maximumCount: Int, exposures: [TPExposureInfo], completion: @escaping ([TPExposureInfo]?, Swift.Error?) -> Void) { session.getExposureInfo(withMaximumCount: maximumCount) { newExposures, inDone, error in - if let error = error { + guard let newExposures = newExposures else { completion(exposures, error) return } - let allExposures = exposures + (newExposures ?? []) + let allExposures = exposures + newExposures.map { $0.tpExposureInfo } if inDone { completion(allExposures, nil) @@ -211,11 +203,9 @@ extension ContactTraceManager { } } - private func saveNewInfectedKeys(keys: [DataManager.TemporaryExposureKey], completion: @escaping (_ numNewRemoteKeys: Int, Swift.Error?) -> Void) { + private func saveNewInfectedKeys(keys: [TPTemporaryExposureKey], completion: @escaping (_ numNewRemoteKeys: Int, Swift.Error?) -> Void) { - let k: [DataManager.TemporaryExposureKey] = keys.map { .init(keyData: $0.keyData, rollingStartNumber: $0.rollingStartNumber) } - - DataManager.shared.saveInfectedKeys(keys: k) { numNewKeys, error in + DataManager.shared.saveInfectedKeys(keys: keys) { numNewKeys, error in if let error = error { completion(0, error) return @@ -342,13 +332,22 @@ extension ContactTraceManager { self.enManager?.invalidate() let manager = ENManager() + + switch manager.exposureNotificationStatus { + case .active: print("ACTIVE") + case .bluetoothOff: print("BLUETOOTH OFF") + case .disabled: print("DISABLED") + case .restricted: print("RESTRICTED") + case .unknown: print("UNKNOWN") + } + self.enManager = manager manager.activate { error in - + if let error = error { manager.invalidate() - + self.isUpdatingEnabledState = false self.isContactTracingEnabled = false completion(error) @@ -359,6 +358,8 @@ extension ContactTraceManager { if let error = error { manager.invalidate() + print("ERROR: \(error)") + self.isUpdatingEnabledState = false self.isContactTracingEnabled = false completion(error) @@ -513,10 +514,10 @@ extension ENExposureDetectionSession { } extension ContactTraceManager { - func retrieveSelfDiagnosisKeys(completion: @escaping ([DataManager.TemporaryExposureKey]?, Swift.Error?) -> Void) { + func retrieveSelfDiagnosisKeys(completion: @escaping ([TPTemporaryExposureKey]?, Swift.Error?) -> Void) { guard let manager = self.enManager else { - // TODO: Handle this error better + // XXX: Shouldn't get here, but handle this error better completion(nil, nil) return } @@ -527,7 +528,7 @@ extension ContactTraceManager { return } - let k: [DataManager.TemporaryExposureKey] = keys.map { .init(keyData: $0.keyData, rollingStartNumber: $0.rollingStartNumber) } + let k: [TPTemporaryExposureKey] = keys.map { $0.tpExposureKey } completion(k, nil) } diff --git a/TracePrivately/Classes/DataManager.swift b/TracePrivately/Classes/DataManager.swift index 6376a09..9d98ce3 100644 --- a/TracePrivately/Classes/DataManager.swift +++ b/TracePrivately/Classes/DataManager.swift @@ -4,7 +4,6 @@ // import CoreData -import ExposureNotification class DataManager { static let shared = DataManager() @@ -102,24 +101,9 @@ extension DataManager { } } } - - // TODO: Refactor this a bit more neatly - struct TemporaryExposureKey { - let keyData: Data - let rollingStartNumber: ENIntervalNumber - // TODO: Support risk level - } - - struct ExposureInfo { - let attenuationValue: ENAttenuation - let date: Date - let duration: TimeInterval - let totalRiskScore: ENRiskScore - let transmissionRiskLevel: ENRiskLevel - } - func saveInfectedKeys(keys: [TemporaryExposureKey], completion: @escaping (_ numNewKeys: Int, _ error: Swift.Error?) -> Void) { + func saveInfectedKeys(keys: [TPTemporaryExposureKey], completion: @escaping (_ numNewKeys: Int, _ error: Swift.Error?) -> Void) { guard keys.count > 0 else { completion(0, nil) @@ -194,6 +178,7 @@ extension DataManager { entity.infectedKey = data // Core data doesn't support unsigned ints, so using Int64 instead of UInt32 entity.rollingStartNumber = Int64(key.rollingStartNumber) + entity.transmissionRiskLevel = Int16(key.transmissionRiskLevel.rawValue) numNewKeys += 1 } @@ -221,7 +206,7 @@ extension DataManager { } } - func allInfectedKeys(completion: @escaping ([TemporaryExposureKey]?, Swift.Error?) -> Void) { + func allInfectedKeys(completion: @escaping ([TPTemporaryExposureKey]?, Swift.Error?) -> Void) { let context = self.persistentContainer.newBackgroundContext() context.perform { @@ -230,7 +215,7 @@ extension DataManager { do { let entities = try context.fetch(request) - let keys: [TemporaryExposureKey] = entities.compactMap { $0.temporaryExposureKey } + let keys: [TPTemporaryExposureKey] = entities.compactMap { $0.temporaryExposureKey } completion(keys, nil) } catch { @@ -241,33 +226,39 @@ extension DataManager { } extension RemoteInfectedKeyEntity { - var temporaryExposureKey: DataManager.TemporaryExposureKey? { + var temporaryExposureKey: TPTemporaryExposureKey? { guard let keyData = self.infectedKey else { return nil } + let riskLevel: TPRiskLevel? = TPRiskLevel(rawValue: UInt8(self.transmissionRiskLevel)) + return .init( keyData: keyData, - rollingStartNumber: ENIntervalNumber(self.rollingStartNumber) + rollingStartNumber: TPIntervalNumber(self.rollingStartNumber), + transmissionRiskLevel: riskLevel ?? .invalid ) } } extension LocalInfectionKeyEntity { - var temporaryExposureKey: DataManager.TemporaryExposureKey? { + var temporaryExposureKey: TPTemporaryExposureKey? { guard let keyData = self.infectedKey else { return nil } + let riskLevel: TPRiskLevel? = TPRiskLevel(rawValue: UInt8(self.transmissionRiskLevel)) + return .init( keyData: keyData, - rollingStartNumber: ENIntervalNumber(self.rollingStartNumber) + rollingStartNumber: TPIntervalNumber(self.rollingStartNumber), + transmissionRiskLevel: riskLevel ?? .invalid ) } } extension DataManager { - func submitReport(formData: InfectedKeysFormData, keys: [DataManager.TemporaryExposureKey], completion: @escaping (Bool, Swift.Error?) -> Void) { + func submitReport(formData: InfectedKeysFormData, keys: [TPTemporaryExposureKey], completion: @escaping (Bool, Swift.Error?) -> Void) { let context = self.persistentContainer.newBackgroundContext() context.perform { @@ -293,6 +284,7 @@ extension DataManager { let keyEntity = LocalInfectionKeyEntity(context: context) keyEntity.infectedKey = key.keyData keyEntity.rollingStartNumber = Int64(key.rollingStartNumber) + keyEntity.transmissionRiskLevel = Int16(key.transmissionRiskLevel.rawValue) keyEntity.infection = entity } @@ -334,14 +326,14 @@ extension DataManager { case sent = "S" } - func saveExposures(exposures: [ENExposureInfo], completion: @escaping (Error?) -> Void) { + func saveExposures(exposures: [TPExposureInfo], completion: @escaping (Error?) -> Void) { let context = self.persistentContainer.newBackgroundContext() context.perform { var delete: [ExposureContactInfoEntity] = [] - var insert: [ENExposureInfo] = [] + var insert: [TPExposureInfo] = [] do { let request = ExposureFetchRequest(includeStatuses: [], includeNotificationStatuses: [], sortDirection: nil) @@ -473,21 +465,23 @@ extension DataManager { } extension ExposureContactInfoEntity { - var contactInfo: DataManager.ExposureInfo? { + var contactInfo: TPExposureInfo? { guard let timestamp = self.timestamp else { return nil } + let transmissionRiskLevel: TPRiskLevel? = TPRiskLevel(rawValue: UInt8(self.transmissionRiskLevel)) + return .init( attenuationValue: UInt8(self.attenuationValue), date: timestamp, duration: self.duration, - totalRiskScore: .zero, // TODO: Fix - transmissionRiskLevel: .low // TODO: Fix + totalRiskScore: TPRiskScore(self.totalRiskScore), + transmissionRiskLevel: transmissionRiskLevel ?? .invalid ) } - func matches(exposure: ENExposureInfo) -> Bool { + func matches(exposure: TPExposureInfo) -> Bool { if exposure.attenuationValue != UInt8(self.attenuationValue) { return false } diff --git a/TracePrivately/Classes/ENMockFramework.swift b/TracePrivately/Classes/ENMockFramework.swift index af6eaf5..259af19 100644 --- a/TracePrivately/Classes/ENMockFramework.swift +++ b/TracePrivately/Classes/ENMockFramework.swift @@ -5,10 +5,8 @@ import Foundation import UIKit - /* -enum ENErrorCode { - case success +enum ENErrorCode: Int { case unknown case badParameter case notEntitled @@ -19,12 +17,12 @@ enum ENErrorCode { case insufficientStorage case notEnabled case apiMisuse - case internalError + case `internal` case insufficientMemory + case rateLimited var localizedTitle: String { switch self { - case .success: return "Success" case .unknown: return "Unknown" case .badParameter: return "Bad Parameter" case .notEntitled: return "Not Entitled" @@ -35,8 +33,9 @@ enum ENErrorCode { case .insufficientStorage: return "Insufficient Storage" case .notEnabled: return "Not Enabled" case .apiMisuse: return "API Miuse" - case .internalError: return "Internal Error" + case .internal: return "Internal Error" case .insufficientMemory: return "Insufficient Memory" + case .rateLimited: return "Rate Limited" } } } @@ -79,16 +78,36 @@ protocol ENAuthorizable { typealias ENIntervalNumber = UInt32 -enum ENRiskLevel { - case invalid - case lowest - case low - case lowMedium - case medium - case mediumHigh - case high - case veryHigh - case highest +typealias ENAttenuation = UInt8 + +public enum ENRiskLevel : UInt8 { + + + case invalid = 0 /// Invalid level. Used when it isn't available. + + /// Invalid level. Used when it isn't available. + case lowest = 1 /// Lowest risk. + + /// Lowest risk. + case low = 10 /// Low risk. + + /// Low risk. + case lowMedium = 25 /// Risk between low and medium. + + /// Risk between low and medium. + case medium = 50 /// Medium risk. + + /// Medium risk. + case mediumHigh = 65 /// Risk between medium and high. + + /// Risk between medium and high. + case high = 80 /// High risk. + + /// High risk. + case veryHigh = 90 /// Very high risk. + + /// Very high risk. + case highest = 100 /// Highest risk. } class ENTemporaryExposureKey { @@ -168,17 +187,101 @@ class ENManager: ENBaseRequest { } } +typealias ENRiskScore = UInt8 + struct ENExposureDetectionSummary { let daysSinceLastExposure: Int let matchedKeyCount: UInt64 + let maximumRiskScore: ENRiskScore // TODO: Make use of this. } typealias ENExposureDetectionFinishCompletion = ((ENExposureDetectionSummary?, Swift.Error?) -> Void) typealias ENGetExposureInfoCompletion = (([ENExposureInfo]?, Bool, Swift.Error?) -> Void) -struct ENExposureConfiguration { +class ENExposureConfiguration { + init() { + + } + + /// Minimum risk score. Excludes exposure incidents with scores lower than this. Defaults to no minimum. + var minimumRiskScore: ENRiskScore + + //--------------------------------------------------------------------------------------------------------------------------- + /** @brief Scores for attenuation buckets. Must contain 8 scores, one for each bucket as defined below: + + attenuationScores[0] when Attenuation > 73. + attenuationScores[1] when 73 >= Attenuation > 63. + attenuationScores[2] when 63 >= Attenuation > 51. + attenuationScores[3] when 51 >= Attenuation > 33. + attenuationScores[4] when 33 >= Attenuation > 27. + attenuationScores[5] when 27 >= Attenuation > 15. + attenuationScores[6] when 15 >= Attenuation > 10. + attenuationScores[7] when 10 >= Attenuation. + */ + var attenuationScores: [NSNumber] + + + /// Weight to apply to the attenuation score. Must be in the range 0-100. + var attenuationWeight: Double + + + //--------------------------------------------------------------------------------------------------------------------------- + /** @brief Scores for days since last exposure buckets. Must contain 8 scores, one for each bucket as defined below: + + daysSinceLastExposureScores[0] when Days >= 14. + daysSinceLastExposureScores[1] else Days >= 12 + daysSinceLastExposureScores[2] else Days >= 10 + daysSinceLastExposureScores[3] else Days >= 8 + daysSinceLastExposureScores[4] else Days >= 6 + daysSinceLastExposureScores[5] else Days >= 4 + daysSinceLastExposureScores[6] else Days >= 2 + daysSinceLastExposureScores[7] else Days >= 0 + */ + var daysSinceLastExposureScores: [NSNumber] + + + /// Weight to apply to the days since last exposure score. Must be in the range 0-100. + var daysSinceLastExposureWeight: Double + + + //--------------------------------------------------------------------------------------------------------------------------- + /** @brief Scores for duration buckets. Must contain 8 scores, one for each bucket as defined below: + + durationScores[0] when Duration == 0 + durationScores[1] else Duration <= 5 + durationScores[2] else Duration <= 10 + durationScores[3] else Duration <= 15 + durationScores[4] else Duration <= 20 + durationScores[5] else Duration <= 25 + durationScores[6] else Duration <= 30 + durationScores[7] else Duration > 30 + */ + var durationScores: [NSNumber] + + + /// Weight to apply to the duration score. Must be in the range 0-100. + var durationWeight: Double + + + //--------------------------------------------------------------------------------------------------------------------------- + /** @brief Scores for transmission risk buckets. Must contain 8 scores, one for each bucket as defined below: + + transmissionRiskScores[0] for ENRiskLevelLowest. + transmissionRiskScores[1] for ENRiskLevelLow. + transmissionRiskScores[2] for ENRiskLevelLowMedium. + transmissionRiskScores[3] for ENRiskLevelMedium. + transmissionRiskScores[4] for ENRiskLevelMediumHigh. + transmissionRiskScores[5] for ENRiskLevelHigh. + transmissionRiskScores[6] for ENRiskLevelVeryHigh. + transmissionRiskScores[7] for ENRiskLevelHighest. + */ + var transmissionRiskScores: [NSNumber] + + + /// Weight to apply to the transmission risk score. Must be in the range 0-100. + var transmissionRiskWeight: Double } class ENExposureDetectionSession: ENBaseRequest { @@ -222,7 +325,8 @@ class ENExposureDetectionSession: ENBaseRequest { let summary = ENExposureDetectionSummary( daysSinceLastExposure: 0, - matchedKeyCount: UInt64(min(Self.maximumFakeMatches, keys.count)) + matchedKeyCount: UInt64(min(Self.maximumFakeMatches, keys.count)), + maximumRiskScore: 8 ) completionHandler(summary, nil) @@ -283,7 +387,7 @@ class ENExposureDetectionSession: ENBaseRequest { } struct ENExposureInfo { - let attenuationValue: UInt8 + let attenuationValue: ENAttenuation let date: Date let duration: TimeInterval } @@ -555,4 +659,5 @@ extension String { } } + */ diff --git a/TracePrivately/Classes/ExposureDataTypes.swift b/TracePrivately/Classes/ExposureDataTypes.swift new file mode 100644 index 0000000..53f985a --- /dev/null +++ b/TracePrivately/Classes/ExposureDataTypes.swift @@ -0,0 +1,55 @@ +// +// ExposureDataTypes.swift +// TracePrivately +// + +import Foundation +import ExposureNotification + +// These types map to the ExposureNotification framework so it can easily be factored out + +typealias TPIntervalNumber = ENIntervalNumber +typealias TPAttenuation = ENAttenuation +typealias TPRiskScore = ENRiskScore +typealias TPRiskLevel = ENRiskLevel + +struct TPTemporaryExposureKey { + let keyData: Data + let rollingStartNumber: TPIntervalNumber + let transmissionRiskLevel: TPRiskLevel! +} + +extension TPTemporaryExposureKey { + var enExposureKey: ENTemporaryExposureKey { + let key = ENTemporaryExposureKey() + key.keyData = keyData + key.rollingStartNumber = rollingStartNumber + key.transmissionRiskLevel = transmissionRiskLevel + + return key + } +} + +extension ENTemporaryExposureKey { + var tpExposureKey: TPTemporaryExposureKey { + return .init( + keyData: keyData, + rollingStartNumber: rollingStartNumber, + transmissionRiskLevel: transmissionRiskLevel + ) + } +} + +struct TPExposureInfo { + let attenuationValue: TPAttenuation + let date: Date + let duration: TimeInterval + let totalRiskScore: TPRiskScore + let transmissionRiskLevel: TPRiskLevel +} + +extension ENExposureInfo { + var tpExposureInfo: TPExposureInfo { + return .init(attenuationValue: attenuationValue, date: date, duration: duration, totalRiskScore: totalRiskScore, transmissionRiskLevel: transmissionRiskLevel) + } +} diff --git a/TracePrivately/Classes/KeyServer/KeyServer.swift b/TracePrivately/Classes/KeyServer/KeyServer.swift index 98a92b3..dcc1552 100644 --- a/TracePrivately/Classes/KeyServer/KeyServer.swift +++ b/TracePrivately/Classes/KeyServer/KeyServer.swift @@ -191,7 +191,7 @@ extension KeyServer { Refer to `KeyServer.yaml` for expected request and response format. */ - func submitInfectedKeys(formData: InfectedKeysFormData, keys: [DataManager.TemporaryExposureKey], previousSubmissionId: String?, completion: @escaping (Bool, String?, Swift.Error?) -> Void) { + func submitInfectedKeys(formData: InfectedKeysFormData, keys: [TPTemporaryExposureKey], previousSubmissionId: String?, completion: @escaping (Bool, String?, Swift.Error?) -> Void) { self._submitInfectedKeys(formData: formData, keys: keys, previousSubmissionId: previousSubmissionId) { success, submissionId, error in if let error = error as? KeyServer.Error, error.shouldRetryWithAuthRequest { @@ -212,7 +212,7 @@ extension KeyServer { } } - private func _submitInfectedKeys(formData: InfectedKeysFormData, keys: [DataManager.TemporaryExposureKey], previousSubmissionId: String?, completion: @escaping (Bool, String?, Swift.Error?) -> Void) { + private func _submitInfectedKeys(formData: InfectedKeysFormData, keys: [TPTemporaryExposureKey], previousSubmissionId: String?, completion: @escaping (Bool, String?, Swift.Error?) -> Void) { guard let endPoint = self.config.submitInfected else { completion(false, nil, Error.invalidConfig) @@ -225,7 +225,8 @@ extension KeyServer { let encodedKeys: [[String: Any]] = keys.map { key in return [ "d": key.keyData.base64EncodedString(), - "r": key.rollingStartNumber + "r": key.rollingStartNumber, + "l": key.transmissionRiskLevel.rawValue ] } @@ -310,8 +311,8 @@ extension KeyServer { struct InfectedKeysResponse { let date: Date - let keys: [DataManager.TemporaryExposureKey] - let deletedKeys: [DataManager.TemporaryExposureKey] + let keys: [TPTemporaryExposureKey] + let deletedKeys: [TPTemporaryExposureKey] } func retrieveInfectedKeys(since date: Date?, completion: @escaping (InfectedKeysResponse?, Swift.Error?) -> Void) { @@ -398,7 +399,7 @@ extension KeyServer { return } - let keys: [DataManager.TemporaryExposureKey] = keysData.compactMap { DataManager.TemporaryExposureKey(jsonData: $0) } + let keys: [TPTemporaryExposureKey] = keysData.compactMap { TPTemporaryExposureKey(jsonData: $0) } print("Found \(keys.count) key(s)") @@ -415,7 +416,8 @@ extension KeyServer { } } -extension DataManager.TemporaryExposureKey { +// TODO: Ensure server supports this value +extension TPTemporaryExposureKey { init?(jsonData: [String: Any]) { guard let base64str = jsonData["d"] as? String, let keyData = Data(base64Encoded: base64str) else { return nil @@ -425,11 +427,24 @@ extension DataManager.TemporaryExposureKey { return nil } - self.init(keyData: keyData, rollingStartNumber: rollingStartNumber) + let riskLevel: TPRiskLevel? + + if let val = jsonData["l"] as? UInt8 { + riskLevel = TPRiskLevel(rawValue: val) + } + else { + riskLevel = nil + } + + self.init( + keyData: keyData, + rollingStartNumber: rollingStartNumber, + transmissionRiskLevel: riskLevel ?? .invalid + ) } } -extension DataManager.TemporaryExposureKey { +extension TPTemporaryExposureKey { // TODO: Implement this so data can be read off the wire in binary format // init?(networkData: Data) { // diff --git a/TracePrivately/Classes/View Controllers/ExposedViewController.swift b/TracePrivately/Classes/View Controllers/ExposedViewController.swift index 66fa3b7..a7688fe 100644 --- a/TracePrivately/Classes/View Controllers/ExposedViewController.swift +++ b/TracePrivately/Classes/View Controllers/ExposedViewController.swift @@ -19,7 +19,7 @@ class ExposedViewController: UICollectionViewController { } enum CellType { - case contact(DataManager.ExposureInfo) + case contact(TPExposureInfo) case intro(String) case nextSteps } @@ -70,7 +70,7 @@ class ExposedViewController: UICollectionViewController { entities = [] } - let contacts: [DataManager.ExposureInfo] = entities.compactMap { $0.contactInfo } + let contacts: [TPExposureInfo] = entities.compactMap { $0.contactInfo } if contacts.count == 0 { let title = String(format: NSLocalizedString("exposure.none.message", comment: ""), Disease.current.localizedTitle) diff --git a/TracePrivately/Classes/View Controllers/MainViewController.swift b/TracePrivately/Classes/View Controllers/MainViewController.swift index 1492575..abec9be 100644 --- a/TracePrivately/Classes/View Controllers/MainViewController.swift +++ b/TracePrivately/Classes/View Controllers/MainViewController.swift @@ -4,7 +4,6 @@ // import UIKit -import ExposureNotification class MainViewController: UIViewController { @@ -279,18 +278,18 @@ extension MainViewController { let haptics = UINotificationFeedbackGenerator() haptics.notificationOccurred(.success) - + ContactTraceManager.shared.startTracing { error in if let error = error { - if let error = error as? ENError, error.code == .notAuthorized { - - } - else { - let alert = UIAlertController(title: NSLocalizedString("error", comment: ""), message: error.localizedDescription, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default, handler: nil)) - - self.present(alert, animated: true, completion: nil) - } + print("Error: \(error)") + + // Will show alert on permission denied as it's not possible to tell if the user made the decision now or earlier. + // Perhaps include instruction on how to resolve this + + let alert = UIAlertController(title: NSLocalizedString("error", comment: ""), message: error.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default, handler: nil)) + + self.present(alert, animated: true, completion: nil) } } } diff --git a/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift b/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift index f40bb08..ce69657 100644 --- a/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift +++ b/TracePrivately/Classes/View Controllers/SubmitInfectionViewController.swift @@ -4,7 +4,6 @@ // import UIKit -import ExposureNotification class SubmitInfectionViewController: UIViewController { @@ -406,20 +405,7 @@ extension SubmitInfectionViewController { ContactTraceManager.shared.retrieveSelfDiagnosisKeys { keys, error in DispatchQueue.main.async { guard let keys = keys else { - var showError = true - - if let error = error as? ENError { - switch error.code { - case .notAuthorized: - showError = false - default: - break - } - } - - if showError { - self.presentErrorAlert(title: nil, message: error?.localizedDescription ?? NSLocalizedString("infection.report.gathering_data.error", comment: "")) - } + self.presentErrorAlert(title: nil, message: error?.localizedDescription ?? NSLocalizedString("infection.report.gathering_data.error", comment: "")) completion(false, error) return @@ -482,7 +468,7 @@ extension SubmitInfectionViewController { } extension SubmitInfectionViewController { - func submitReport(keys: [DataManager.TemporaryExposureKey], completion: @escaping (Bool, Swift.Error?) -> Void) { + func submitReport(keys: [TPTemporaryExposureKey], completion: @escaping (Bool, Swift.Error?) -> Void) { let formData = self.gatherFormData diff --git a/TracePrivately/TracePrivately.entitlements b/TracePrivately/TracePrivately.entitlements index c97806a..95c1713 100644 --- a/TracePrivately/TracePrivately.entitlements +++ b/TracePrivately/TracePrivately.entitlements @@ -2,7 +2,6 @@ - com.apple.developer.exposure-notification - + diff --git a/TracePrivately/TracePrivately.xcdatamodeld/.xccurrentversion b/TracePrivately/TracePrivately.xcdatamodeld/.xccurrentversion index f5b3fac..e46b68c 100644 --- a/TracePrivately/TracePrivately.xcdatamodeld/.xccurrentversion +++ b/TracePrivately/TracePrivately.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Model 7.xcdatamodel + Model 8.xcdatamodel diff --git a/TracePrivately/TracePrivately.xcdatamodeld/Model 8.xcdatamodel/contents b/TracePrivately/TracePrivately.xcdatamodeld/Model 8.xcdatamodel/contents new file mode 100644 index 0000000..6682952 --- /dev/null +++ b/TracePrivately/TracePrivately.xcdatamodeld/Model 8.xcdatamodel/contents @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 55b945bdf26e95d0b17999ae2f0cac125ea7ea5e Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 10:38:02 +0930 Subject: [PATCH 8/9] Update server to support risk level value --- KeyServer/KeyServer.yaml | 6 ++ KeyServer/sample/htdocs/infected.php | 8 ++- KeyServer/sample/htdocs/submit.php | 6 +- KeyServer/sample/schema.sql | 1 + TracePrivately/Classes/ENMockFramework.swift | 58 ++++++++++++++++++-- TracePrivately/KeyServer.plist | 2 +- 6 files changed, 70 insertions(+), 11 deletions(-) diff --git a/KeyServer/KeyServer.yaml b/KeyServer/KeyServer.yaml index 9293367..2388202 100644 --- a/KeyServer/KeyServer.yaml +++ b/KeyServer/KeyServer.yaml @@ -139,6 +139,9 @@ paths: type: integer example: 1234 description: The rolling start number for the key. + l: + type: integer + description: Represents the calculated risk level from 1-100 deleted_keys: description: | A list of keys that have subsequently been marked as not infected. Client should remove them from their cache. @@ -214,6 +217,9 @@ paths: type: integer example: 1234 description: The rolling start number for the key. + l: + type: integer + description: Represents the calculated risk level from 1-100 form: type: array description: Additional form data gathered from user. Each item diff --git a/KeyServer/sample/htdocs/infected.php b/KeyServer/sample/htdocs/infected.php index 4b5f013..530c018 100644 --- a/KeyServer/sample/htdocs/infected.php +++ b/KeyServer/sample/htdocs/infected.php @@ -33,7 +33,7 @@ } } -$stmt = $db->prepare('SELECT infected_key, rolling_start_number FROM infected_keys WHERE status = :s AND status_updated >= :t'); +$stmt = $db->prepare('SELECT infected_key, rolling_start_number, risk_level FROM infected_keys WHERE status = :s AND status_updated >= :t'); $stmt->bindValue(':t', $time, SQLITE3_INTEGER); $stmt->bindValue(':s', 'A', SQLITE3_TEXT); @@ -48,7 +48,8 @@ while (($row = $result->fetchArray(SQLITE3_NUM))) { $keys[] = array( 'd' => base64_decode($row[0]), - 'r' => (int) $row[1] + 'r' => (int) $row[1], + 'l' => (int) $row[2] ); } } @@ -56,7 +57,8 @@ while (($row = $result->fetchArray(SQLITE3_NUM))) { $keys[] = array( 'd' => $row[0], - 'r' => (int) $row[1] + 'r' => (int) $row[1], + 'l' => (int) $row[2] ); } } diff --git a/KeyServer/sample/htdocs/submit.php b/KeyServer/sample/htdocs/submit.php index dadbfbc..036032f 100644 --- a/KeyServer/sample/htdocs/submit.php +++ b/KeyServer/sample/htdocs/submit.php @@ -1,6 +1,8 @@ lastInsertRowID(); - $stmt = $db->prepare('INSERT INTO infected_keys (infected_key, rolling_start_number, timestamp, status, status_updated, submission_id) VALUES (:k, :r, :t, :s, :d, :i)'); + $stmt = $db->prepare('INSERT INTO infected_keys (infected_key, rolling_start_number, risk_level, timestamp, status, status_updated, submission_id) VALUES (:k, :r, :l, :t, :s, :d, :i)'); // It's possible there are no keys with a submission, and a placeholder record is created so subsequent keys can be recorded foreach ($json['keys'] as $key) { $encodedKey = $key['d']; $rollingStartNumber = $key['r']; + $riskLevel = $key['l']; $stmt->bindValue(':k', $encodedKey, SQLITE3_TEXT); $stmt->bindValue(':r', $rollingStartNumber, SQLITE3_INTEGER); + $stmt->bindValue(':l', $riskLevel, SQLITE3_INTEGER); $stmt->bindValue(':t', $time, SQLITE3_INTEGER); $stmt->bindValue(':s', 'P', SQLITE3_TEXT); // Pending state, must be approved $stmt->bindValue(':d', $time, SQLITE3_INTEGER); diff --git a/KeyServer/sample/schema.sql b/KeyServer/sample/schema.sql index 8d7f29c..194c8a8 100644 --- a/KeyServer/sample/schema.sql +++ b/KeyServer/sample/schema.sql @@ -29,6 +29,7 @@ CREATE INDEX infected_key_submissions_timestamp ON infected_key_submissions (tim CREATE TABLE infected_keys ( infected_key STRING, rolling_start_number INTEGER, + risk_level INTEGER, status TEXT, timestamp INTEGER, status_updated INTEGER, diff --git a/TracePrivately/Classes/ENMockFramework.swift b/TracePrivately/Classes/ENMockFramework.swift index 259af19..36aeefb 100644 --- a/TracePrivately/Classes/ENMockFramework.swift +++ b/TracePrivately/Classes/ENMockFramework.swift @@ -5,7 +5,7 @@ import Foundation import UIKit -/* + enum ENErrorCode: Int { case unknown case badParameter @@ -158,8 +158,41 @@ extension ENTemporaryExposureKey { typealias ENGetDiagnosisKeysHandler = ([ENTemporaryExposureKey]?, Error?) -> Void +enum ENStatus : Int { + + + /// Status of Exposure Notification is unknown. This is the status before ENManager has activated successfully. + case unknown = 0 + + + /// Exposure Notification is active on the system. + case active = 1 + + + /// Exposure Notification is disabled. setExposureNotificationEnabled:completionHandler can be used to enable it. + case disabled = 2 + + + /// Bluetooth has been turned off on the system. Bluetooth is required for Exposure Notification. + /// Note: this may not match the state of Bluetooth as reported by CoreBluetooth. + /// Exposure Notification is a system service and can use Bluetooth in situations when apps cannot. + /// So for the purposes of Exposure Notification, it's better to use this API instead of CoreBluetooth. + case bluetoothOff = 3 + + + /// Exposure Notification is not active due to system restrictions, such as parental controls. + /// When in this state, the app cannot enable Exposure Notification. + case restricted = 4 +} + + class ENManager: ENBaseRequest { + var exposureNotificationStatus: ENStatus { + // TODO: Implement properly + return .active + } + func setExposureNotificationEnabled(_ flag: Bool, completion: @escaping ENErrorHandler) { // TODO: Doesn't do anything // TODO: Request permission here @@ -201,7 +234,15 @@ typealias ENGetExposureInfoCompletion = (([ENExposureInfo]?, Bool, Swift.Error?) class ENExposureConfiguration { init() { - + self.minimumRiskScore = 0 + self.attenuationScores = [] + self.attenuationWeight = 0 + self.daysSinceLastExposureScores = [] + self.daysSinceLastExposureWeight = 0 + self.durationScores = [] + self.durationWeight = 0 + self.transmissionRiskScores = [] + self.transmissionRiskWeight = 0 } /// Minimum risk score. Excludes exposure incidents with scores lower than this. Defaults to no minimum. @@ -375,7 +416,13 @@ class ENExposureDetectionSession: ENBaseRequest { let date = Date(timeIntervalSince1970: TimeInterval(key.rollingStartNumber * 600)) let duration: TimeInterval = 15 * 60 - return ENExposureInfo(attenuationValue: 0, date: date, duration: duration) + return ENExposureInfo( + attenuationValue: 0, + date: date, + duration: duration, + totalRiskScore: 51, + transmissionRiskLevel: .medium + ) } let inDone = toIndex >= allMatchedKeys.count @@ -390,6 +437,8 @@ struct ENExposureInfo { let attenuationValue: ENAttenuation let date: Date let duration: TimeInterval + let totalRiskScore: ENRiskScore + let transmissionRiskLevel: ENRiskLevel } class ENSelfExposureResetRequest: ENAuthorizableBaseRequest { @@ -658,6 +707,3 @@ extension String { } } } - - -*/ diff --git a/TracePrivately/KeyServer.plist b/TracePrivately/KeyServer.plist index 0bcf063..c43a902 100644 --- a/TracePrivately/KeyServer.plist +++ b/TracePrivately/KeyServer.plist @@ -3,7 +3,7 @@ BaseUrl - https://example.com/api/ + https://trace.crunchybagel.dev/api/ EndPoints GetInfectedKeys From 0cb8ea35faab0472ab91af518ec57bb5bab6263a Mon Sep 17 00:00:00 2001 From: Quentin Zervaas Date: Thu, 30 Apr 2020 10:48:41 +0930 Subject: [PATCH 9/9] Misc cleanup --- TracePrivately.xcodeproj/project.pbxproj | 4 ++-- TracePrivately/Classes/ContactTraceManager.swift | 2 ++ TracePrivately/Classes/ENMockFramework.swift | 3 +++ TracePrivately/Classes/ExposureDataTypes.swift | 2 ++ .../Classes/View Controllers/MainViewController.swift | 8 +++++++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/TracePrivately.xcodeproj/project.pbxproj b/TracePrivately.xcodeproj/project.pbxproj index 83af750..7f118b6 100644 --- a/TracePrivately.xcodeproj/project.pbxproj +++ b/TracePrivately.xcodeproj/project.pbxproj @@ -641,7 +641,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = CHZS3BHM57; INFOPLIST_FILE = TracePrivately/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -663,7 +663,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = CHZS3BHM57; INFOPLIST_FILE = TracePrivately/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/TracePrivately/Classes/ContactTraceManager.swift b/TracePrivately/Classes/ContactTraceManager.swift index f1db15a..26a6b38 100644 --- a/TracePrivately/Classes/ContactTraceManager.swift +++ b/TracePrivately/Classes/ContactTraceManager.swift @@ -6,7 +6,9 @@ import Foundation import UserNotifications import UIKit +#if canImport(ExposureNotification) import ExposureNotification +#endif class ContactTraceManager: NSObject { diff --git a/TracePrivately/Classes/ENMockFramework.swift b/TracePrivately/Classes/ENMockFramework.swift index 36aeefb..29b096d 100644 --- a/TracePrivately/Classes/ENMockFramework.swift +++ b/TracePrivately/Classes/ENMockFramework.swift @@ -6,6 +6,8 @@ import Foundation import UIKit +/// To use the real ExposureNotifications framework, just comment out this entire file. You must be running iOS 13.4 or newer + enum ENErrorCode: Int { case unknown case badParameter @@ -707,3 +709,4 @@ extension String { } } } + diff --git a/TracePrivately/Classes/ExposureDataTypes.swift b/TracePrivately/Classes/ExposureDataTypes.swift index 53f985a..a6592f7 100644 --- a/TracePrivately/Classes/ExposureDataTypes.swift +++ b/TracePrivately/Classes/ExposureDataTypes.swift @@ -4,7 +4,9 @@ // import Foundation +#if canImport(ExposureNotification) import ExposureNotification +#endif // These types map to the ExposureNotification framework so it can easily be factored out diff --git a/TracePrivately/Classes/View Controllers/MainViewController.swift b/TracePrivately/Classes/View Controllers/MainViewController.swift index abec9be..39a72f9 100644 --- a/TracePrivately/Classes/View Controllers/MainViewController.swift +++ b/TracePrivately/Classes/View Controllers/MainViewController.swift @@ -174,7 +174,13 @@ class MainViewController: UIViewController { self.submitInfectionContainer.backgroundColor = color } else { - self.view.backgroundColor = .groupTableViewBackground + if #available(iOS 13, *) { + self.view.backgroundColor = .systemGroupedBackground + } + else { + self.view.backgroundColor = .groupTableViewBackground + } + self.tracingContainer.backgroundColor = .white self.submitInfectionContainer.backgroundColor = .white }