handle reduced accuracy authorization case

This commit is contained in:
Mauro Romito
2026-04-20 23:23:04 +02:00
committed by Mauro
parent 9983e23a4c
commit dd7048d5c2
5 changed files with 147 additions and 13 deletions

View File

@@ -10,11 +10,13 @@ import CoreLocation
extension CLLocationManagerMock {
struct Configuration {
var authorizationStatus: CLAuthorizationStatus = .authorizedAlways
var accuracyAuthorization: CLAccuracyAuthorization = .fullAccuracy
}
convenience init(_ configuration: Configuration) {
self.init()
underlyingAuthorizationStatus = configuration.authorizationStatus
underlyingAccuracyAuthorization = configuration.accuracyAuthorization
}
}

View File

@@ -2214,6 +2214,11 @@ class CLLocationManagerMock: CLLocationManagerProtocol, @unchecked Sendable {
set(value) { underlyingAuthorizationStatus = value }
}
var underlyingAuthorizationStatus: CLAuthorizationStatus!
var accuracyAuthorization: CLAccuracyAuthorization {
get { return underlyingAccuracyAuthorization }
set(value) { underlyingAccuracyAuthorization = value }
}
var underlyingAccuracyAuthorization: CLAccuracyAuthorization!
//MARK: - requestAlwaysAuthorization
@@ -2320,6 +2325,76 @@ class CLLocationManagerMock: CLLocationManagerProtocol, @unchecked Sendable {
stopUpdatingLocationCallsCount += 1
stopUpdatingLocationClosure?()
}
//MARK: - startMonitoringSignificantLocationChanges
var startMonitoringSignificantLocationChangesUnderlyingCallsCount = 0
var startMonitoringSignificantLocationChangesCallsCount: Int {
get {
if Thread.isMainThread {
return startMonitoringSignificantLocationChangesUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = startMonitoringSignificantLocationChangesUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
startMonitoringSignificantLocationChangesUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
startMonitoringSignificantLocationChangesUnderlyingCallsCount = newValue
}
}
}
}
var startMonitoringSignificantLocationChangesCalled: Bool {
return startMonitoringSignificantLocationChangesCallsCount > 0
}
var startMonitoringSignificantLocationChangesClosure: (() -> Void)?
func startMonitoringSignificantLocationChanges() {
startMonitoringSignificantLocationChangesCallsCount += 1
startMonitoringSignificantLocationChangesClosure?()
}
//MARK: - stopMonitoringSignificantLocationChanges
var stopMonitoringSignificantLocationChangesUnderlyingCallsCount = 0
var stopMonitoringSignificantLocationChangesCallsCount: Int {
get {
if Thread.isMainThread {
return stopMonitoringSignificantLocationChangesUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = stopMonitoringSignificantLocationChangesUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
stopMonitoringSignificantLocationChangesUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
stopMonitoringSignificantLocationChangesUnderlyingCallsCount = newValue
}
}
}
}
var stopMonitoringSignificantLocationChangesCalled: Bool {
return stopMonitoringSignificantLocationChangesCallsCount > 0
}
var stopMonitoringSignificantLocationChangesClosure: (() -> Void)?
func stopMonitoringSignificantLocationChanges() {
stopMonitoringSignificantLocationChangesCallsCount += 1
stopMonitoringSignificantLocationChangesClosure?()
}
}
class CXProviderMock: CXProviderProtocol, @unchecked Sendable {

View File

@@ -16,10 +16,13 @@ protocol CLLocationManagerProtocol: AnyObject {
var desiredAccuracy: CLLocationAccuracy { get set }
var distanceFilter: CLLocationDistance { get set }
var authorizationStatus: CLAuthorizationStatus { get }
var accuracyAuthorization: CLAccuracyAuthorization { get }
func requestAlwaysAuthorization()
func startUpdatingLocation()
func stopUpdatingLocation()
func startMonitoringSignificantLocationChanges()
func stopMonitoringSignificantLocationChanges()
}
extension CLLocationManager: CLLocationManagerProtocol { }

View File

@@ -9,11 +9,20 @@ import Combine
import CoreLocation
class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationManagerDelegate {
enum LiveState {
case full
case limited
case off
}
private let clientProxy: ClientProxyProtocol
private let locationManager: CLLocationManagerProtocol
private let appSettings: AppSettings
private let authorizationStatusSubject: CurrentValueSubject<CLAuthorizationStatus, Never>
var authorizationStatus: CurrentValuePublisher<CLAuthorizationStatus, Never> {
authorizationStatusSubject.asCurrentValuePublisher()
}
/// Cached joined room proxies keyed by room ID, kept in sync with the active sessions dictionary.
private var activeRoomProxies = [String: JoinedRoomProxyProtocol]()
@@ -23,9 +32,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
private var cancellables = Set<AnyCancellable>()
var authorizationStatus: CurrentValuePublisher<CLAuthorizationStatus, Never> {
authorizationStatusSubject.asCurrentValuePublisher()
}
private var liveState = LiveState.off
@MainActor
init(clientProxy: ClientProxyProtocol,
@@ -118,6 +125,19 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
stopAllSessions()
}
switch (liveState, manager.accuracyAuthorization) {
// If accuracy authorization changed while updates are active, start and stop to switch update method.
case (.full, .reducedAccuracy), (.limited, .fullAccuracy):
stopUpdatingLocation()
if manager.accuracyAuthorization == .fullAccuracy {
// The system has forced reduced desired accuracy so we need to restore the desired value by the user.
setupMinimumDistance(appSettings.liveLocationMinimumDistanceUpdate)
}
startUpdatingLocation()
default:
break
}
authorizationStatusSubject.send(manager.authorizationStatus)
}
@@ -201,22 +221,30 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
locationManager.distanceFilter = CLLocationDistance(minimumDistance)
}
private var isUpdating = false
private func startUpdatingLocation() {
guard !isUpdating else { return }
isUpdating = true
guard liveState == .off else { return }
MXLog.info("Starting live location updates via delegate")
locationManager.startUpdatingLocation()
if locationManager.accuracyAuthorization == .fullAccuracy {
MXLog.info("Starting live location updates with full accuracy")
liveState = .full
locationManager.startUpdatingLocation()
} else {
MXLog.info("Starting live location updates with significant changes (reduced accuracy)")
liveState = .limited
locationManager.startMonitoringSignificantLocationChanges()
}
}
private func stopUpdatingLocation() {
guard isUpdating else { return }
isUpdating = false
if liveState == .full {
MXLog.info("Stopping live location updates (full accuracy)")
locationManager.stopUpdatingLocation()
} else if liveState == .limited {
MXLog.info("Stopping live location updates (reduced accuracy)")
locationManager.stopMonitoringSignificantLocationChanges()
}
MXLog.info("Stopping live location updates")
locationManager.stopUpdatingLocation()
liveState = .off
}
private func sendLocationToActiveRooms(_ coordinate: CLLocationCoordinate2D) async {

View File

@@ -42,6 +42,8 @@ final class LiveLocationManagerTests {
#expect(roomProxy.startLiveLocationShareDurationCalled)
#expect(!roomProxy.stopLiveLocationShareCalled)
#expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room:matrix.org"] != nil)
#expect(locationManagerMock.startUpdatingLocationCalled)
#expect(!locationManagerMock.startMonitoringSignificantLocationChangesCalled)
}
@Test
@@ -131,6 +133,10 @@ final class LiveLocationManagerTests {
#expect(roomProxy.stopLiveLocationShareCalled)
#expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room:matrix.org"] == nil)
// Setting the timeout date above starts tracking; removing it stops tracking.
#expect(locationManagerMock.startUpdatingLocationCalled)
#expect(locationManagerMock.stopUpdatingLocationCalled)
#expect(!locationManagerMock.stopMonitoringSignificantLocationChangesCalled)
}
@Test
@@ -156,6 +162,26 @@ final class LiveLocationManagerTests {
#expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room2:matrix.org"] != nil)
}
// MARK: - Reduced accuracy
@Test
func startLiveLocationInReducedAccuracyMode() async throws {
locationManagerMock.underlyingAccuracyAuthorization = .reducedAccuracy
let roomProxy = makeRoomProxy(roomID: "!room:matrix.org")
clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) }
let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300))
try result.get()
#expect(locationManagerMock.startMonitoringSignificantLocationChangesCalled)
#expect(!locationManagerMock.startUpdatingLocationCalled)
await manager.stopLiveLocation(roomID: "!room:matrix.org")
#expect(locationManagerMock.stopMonitoringSignificantLocationChangesCalled)
#expect(!locationManagerMock.stopUpdatingLocationCalled)
}
// MARK: - Private
private func makeRoomProxy(roomID: String) -> JoinedRoomProxyMock {