Unverified Commit d2dd15fe authored by Stefan Mitterrutzner's avatar Stefan Mitterrutzner Committed by GitHub

Merge pull request #189 from DP-3T/bugfix/retry-activation-and-enable-on-foreground

retry activation and enable on foreground
parents df478b8b 7deff7e4
......@@ -28,6 +28,10 @@ class ExposureNotificationTracer: Tracer {
private let managerClass: ENManager.Type
private var isActivated: Bool = false
private var deferredEnable: Bool?
private(set) var state: TrackingState {
didSet {
guard oldValue != state else { return }
......@@ -42,14 +46,29 @@ class ExposureNotificationTracer: Tracer {
state = .initialization
activateManager()
NotificationCenter.default.addObserver(self,
selector: #selector(willEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil)
}
func activateManager(){
logger.log("calling ENMananger.activate")
manager.activate { [weak self] error in
guard let self = self else { return }
self.queue.async {
if let error = error {
self.logger.error("ENMananger.activate failed error: %{public}@", error.localizedDescription)
self.state = .inactive(error: .exposureNotificationError(error: error))
} else {
self.isActivated = true
self.initializeObservers()
if let deferredEnable = self.deferredEnable {
self.setEnabled(deferredEnable, completionHandler: nil)
}
}
self.logger.log("notify callbacks after initialisation (count: %d)", self.initializationCallbacks.count)
self.initializationCallbacks.forEach {
......@@ -58,10 +77,6 @@ class ExposureNotificationTracer: Tracer {
self.initializationCallbacks.removeAll()
}
}
NotificationCenter.default.addObserver(self,
selector: #selector(willEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil)
}
func addInitialisationCallback(callback: @escaping ()-> Void ){
......@@ -83,7 +98,15 @@ class ExposureNotificationTracer: Tracer {
var isAuthorized: Bool { ENManager.authorizationStatus == .authorized }
@objc func willEnterForeground(){
updateState()
self.queue.async {
if !self.isActivated {
self.activateManager()
} else if let deferredEnable = self.deferredEnable {
self.setEnabled(deferredEnable, completionHandler: nil)
} else {
self.updateState()
}
}
}
func initializeObservers() {
......@@ -99,21 +122,41 @@ class ExposureNotificationTracer: Tracer {
}
func updateState() {
state = .init(state: manager.exposureNotificationStatus,
authorizationStatus: managerClass.authorizationStatus,
enabled: manager.exposureNotificationEnabled)
guard self.isActivated else { return }
self.state = .init(state: self.manager.exposureNotificationStatus,
authorizationStatus: self.managerClass.authorizationStatus,
enabled: self.manager.exposureNotificationEnabled)
}
func setEnabled(_ enabled: Bool, completionHandler: ((Error?) -> Void)?) {
logger.log("calling ENMananger.setExposureNotificationEnabled %{public}@", enabled ? "true" : "false")
guard self.isActivated else {
logger.log("could not enable since manager is not activated")
self.deferredEnable = enabled
// use stored error if available
if case let TrackingState.inactive(error: error) = state {
completionHandler?(error)
} else {
completionHandler?(DP3TTracingError.permissonError)
}
return
}
deferredEnable = nil
manager.setExposureNotificationEnabled(enabled) { [weak self] error in
guard let self = self else { return }
if let error = error {
self.logger.error("ENMananger.setExposureNotificationEnabled failed error: %{public}@", error.localizedDescription)
self.deferredEnable = enabled
self.state = .inactive(error: .exposureNotificationError(error: error))
} else {
self.deferredEnable = nil
self.updateState()
}
self.updateState()
completionHandler?(error)
}
}
......@@ -121,11 +164,6 @@ class ExposureNotificationTracer: Tracer {
extension TrackingState {
init(state: ENStatus, authorizationStatus: ENAuthorizationStatus, enabled: Bool) {
if authorizationStatus == .unknown {
self = .stopped
return
}
guard authorizationStatus == .authorized else {
self = .inactive(error: .permissonError)
return
......
......@@ -101,11 +101,12 @@ class DP3TSDKTests: XCTestCase {
}
func testCallEnable(){
manager.completeActivation()
let exp = expectation(description: "enable")
try! sdk.startTracing { (err) in
exp.fulfill()
}
wait(for: [exp], timeout: 0.1)
wait(for: [exp], timeout: 1.0)
XCTAssertEqual(tracer.state, TrackingState.active)
}
......@@ -201,4 +202,122 @@ class DP3TSDKTests: XCTestCase {
wait(for: [exp], timeout: 1.0)
XCTAssertEqual(service.requests.count, 10)
}
struct MockError: Error, Equatable {
let message: String
}
func testActivationAfterFailure() {
MockENManager.authStatus = .unknown
let message = "mockError"
manager.completeActivation(error: MockError(message: message))
sleep(1)
let expStatus = expectation(description: "status")
sdk.status { (result) in
switch result {
case let .success(state):
switch state.trackingState {
case let .inactive(error: error):
switch error {
case let .exposureNotificationError(error: enError):
guard let mockError = enError as? MockError else {
XCTFail()
return
}
XCTAssertEqual(mockError.message, message)
default:
XCTFail()
}
default:
XCTFail()
}
case .failure(_):
XCTFail()
}
expStatus.fulfill()
}
wait(for: [expStatus], timeout: 0.1)
// app comes again in foreground
tracer.willEnterForeground()
sleep(1)
MockENManager.authStatus = .authorized
manager.completeActivation()
sleep(1)
let expStatusAfter = expectation(description: "statusafter")
sdk.status { (result) in
switch result {
case let .success(state):
switch state.trackingState {
case .stopped:
break;
default:
XCTFail()
}
case .failure(_):
XCTFail()
}
expStatusAfter.fulfill()
}
wait(for: [expStatusAfter], timeout: 0.1)
}
func testEnableAfterEnableFailure(){
manager.enableError = MockError(message: "message")
manager.completeActivation()
let exp = expectation(description: "enable")
try! sdk.startTracing { (err) in
XCTAssert(err != nil)
XCTAssertEqual((err! as! MockError).message, "message")
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
XCTAssertEqual(tracer.state, TrackingState.inactive(error: .exposureNotificationError(error: manager.enableError!)))
manager.enableError = nil
// app comes again in foreground
tracer.willEnterForeground()
sleep(1)
XCTAssertEqual(tracer.state, TrackingState.active)
}
func testEnableAfterActivationFailure(){
MockENManager.authStatus = .unknown
let error = MockError(message: "mockError")
manager.completeActivation(error: error)
sleep(1)
let exp = expectation(description: "enable")
try! sdk.startTracing { (err) in
XCTAssert(err != nil)
switch (err! as! DP3TTracingError) {
case let .exposureNotificationError(error: enError):
XCTAssertEqual(enError as! MockError, error)
default:
XCTFail()
}
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
XCTAssertEqual(tracer.state, TrackingState.inactive(error: .exposureNotificationError(error: error)))
// app comes again in foreground
tracer.willEnterForeground()
sleep(1)
manager.completeActivation()
sleep(1)
XCTAssertEqual(tracer.state, TrackingState.active)
}
}
......@@ -41,6 +41,8 @@ class MockENManager: ENManager {
var summary = MockSummary()
var enableError: Error?
override func detectExposures(configuration _: ENExposureConfiguration, diagnosisKeyURLs: [URL], completionHandler: @escaping ENDetectExposuresHandler) -> Progress {
detectExposuresWasCalled = true
completionHandler(summary, nil)
......@@ -56,14 +58,18 @@ class MockENManager: ENManager {
}
override func setExposureNotificationEnabled(_ enabled: Bool, completionHandler: @escaping ENErrorHandler) {
self.isEnabled = enabled
self.status = .active
Self.authStatus = .authorized
completionHandler(nil)
if let error = enableError {
completionHandler(error)
} else {
self.isEnabled = enabled
self.status = .active
Self.authStatus = .authorized
completionHandler(nil)
}
}
func completeActivation(){
activateCallbacks.forEach{ $0(nil)}
func completeActivation(error: Error? = nil){
activateCallbacks.forEach{ $0(error)}
activateCallbacks.removeAll()
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment