diff --git a/apps/ios/Sources/Location/LocationService.swift b/apps/ios/Sources/Location/LocationService.swift index 03a199d3e196..eb851a3d71cd 100644 --- a/apps/ios/Sources/Location/LocationService.swift +++ b/apps/ios/Sources/Location/LocationService.swift @@ -89,6 +89,7 @@ final class LocationService: NSObject, CLLocationManagerDelegate, LocationServic Task { @MainActor in let clock = ContinuousClock() let noPromptDeadline = clock.now.advanced(by: .milliseconds(1500)) + var activeUndeterminedDeadline: ContinuousClock.Instant? var observedPrompt = UIApplication.shared.applicationState != .active // A slow system prompt must not trigger the no-callback fallback. Once iOS makes // the app inactive, wait until the user dismisses the prompt and the app returns. @@ -97,15 +98,27 @@ final class LocationService: NSObject, CLLocationManagerDelegate, LocationServic let applicationIsActive = UIApplication.shared.applicationState == .active if !applicationIsActive { observedPrompt = true + activeUndeterminedDeadline = nil continue } guard observedPrompt || clock.now >= noPromptDeadline else { continue } let status = self.manager.authorizationStatus - guard Self.shouldCompleteAuthorizationWait( + if Self.shouldCompleteAuthorizationWait( status: status, requiresDeterminedStatus: requiresDeterminedStatus) - else { continue } - self.finishAuthorizationWait(waitID: waitID, status: status) + { + self.finishAuthorizationWait(waitID: waitID, status: status) + continue + } + if observedPrompt, activeUndeterminedDeadline == nil { + activeUndeterminedDeadline = clock.now.advanced(by: .milliseconds(1500)) + } + let fallbackDeadline = activeUndeterminedDeadline ?? noPromptDeadline + guard clock.now >= fallbackDeadline else { continue } + self.finishAuthorizationWait( + waitID: waitID, + status: status, + allowUndeterminedFallback: true) } } } @@ -113,16 +126,22 @@ final class LocationService: NSObject, CLLocationManagerDelegate, LocationServic nonisolated static func shouldCompleteAuthorizationWait( status: CLAuthorizationStatus, - requiresDeterminedStatus: Bool) -> Bool + requiresDeterminedStatus: Bool, + allowUndeterminedFallback: Bool = false) -> Bool { - !requiresDeterminedStatus || status != .notDetermined + allowUndeterminedFallback || !requiresDeterminedStatus || status != .notDetermined } - private func finishAuthorizationWait(waitID: UUID, status: CLAuthorizationStatus) { + private func finishAuthorizationWait( + waitID: UUID, + status: CLAuthorizationStatus, + allowUndeterminedFallback: Bool = false) + { guard self.authWaitID == waitID, let cont = self.authContinuation else { return } guard Self.shouldCompleteAuthorizationWait( status: status, - requiresDeterminedStatus: self.authWaitRequiresDeterminedStatus) + requiresDeterminedStatus: self.authWaitRequiresDeterminedStatus, + allowUndeterminedFallback: allowUndeterminedFallback) else { return } self.authWaitID = nil self.authWaitRequiresDeterminedStatus = false diff --git a/apps/ios/Tests/LocationPermissionSummaryTests.swift b/apps/ios/Tests/LocationPermissionSummaryTests.swift index 526b8bf8dd0d..60dc1b9c120d 100644 --- a/apps/ios/Tests/LocationPermissionSummaryTests.swift +++ b/apps/ios/Tests/LocationPermissionSummaryTests.swift @@ -101,6 +101,10 @@ import Testing #expect(LocationService.shouldCompleteAuthorizationWait( status: .notDetermined, requiresDeterminedStatus: false)) + #expect(LocationService.shouldCompleteAuthorizationWait( + status: .notDetermined, + requiresDeterminedStatus: true, + allowUndeterminedFallback: true)) } @MainActor @Test func `off mode stops significant location monitoring`() async {