handle reduced accuracy authorization case
This commit is contained in:
@@ -10,11 +10,13 @@ import CoreLocation
|
|||||||
extension CLLocationManagerMock {
|
extension CLLocationManagerMock {
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
var authorizationStatus: CLAuthorizationStatus = .authorizedAlways
|
var authorizationStatus: CLAuthorizationStatus = .authorizedAlways
|
||||||
|
var accuracyAuthorization: CLAccuracyAuthorization = .fullAccuracy
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(_ configuration: Configuration) {
|
convenience init(_ configuration: Configuration) {
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
underlyingAuthorizationStatus = configuration.authorizationStatus
|
underlyingAuthorizationStatus = configuration.authorizationStatus
|
||||||
|
underlyingAccuracyAuthorization = configuration.accuracyAuthorization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2214,6 +2214,11 @@ class CLLocationManagerMock: CLLocationManagerProtocol, @unchecked Sendable {
|
|||||||
set(value) { underlyingAuthorizationStatus = value }
|
set(value) { underlyingAuthorizationStatus = value }
|
||||||
}
|
}
|
||||||
var underlyingAuthorizationStatus: CLAuthorizationStatus!
|
var underlyingAuthorizationStatus: CLAuthorizationStatus!
|
||||||
|
var accuracyAuthorization: CLAccuracyAuthorization {
|
||||||
|
get { return underlyingAccuracyAuthorization }
|
||||||
|
set(value) { underlyingAccuracyAuthorization = value }
|
||||||
|
}
|
||||||
|
var underlyingAccuracyAuthorization: CLAccuracyAuthorization!
|
||||||
|
|
||||||
//MARK: - requestAlwaysAuthorization
|
//MARK: - requestAlwaysAuthorization
|
||||||
|
|
||||||
@@ -2320,6 +2325,76 @@ class CLLocationManagerMock: CLLocationManagerProtocol, @unchecked Sendable {
|
|||||||
stopUpdatingLocationCallsCount += 1
|
stopUpdatingLocationCallsCount += 1
|
||||||
stopUpdatingLocationClosure?()
|
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 {
|
class CXProviderMock: CXProviderProtocol, @unchecked Sendable {
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,13 @@ protocol CLLocationManagerProtocol: AnyObject {
|
|||||||
var desiredAccuracy: CLLocationAccuracy { get set }
|
var desiredAccuracy: CLLocationAccuracy { get set }
|
||||||
var distanceFilter: CLLocationDistance { get set }
|
var distanceFilter: CLLocationDistance { get set }
|
||||||
var authorizationStatus: CLAuthorizationStatus { get }
|
var authorizationStatus: CLAuthorizationStatus { get }
|
||||||
|
var accuracyAuthorization: CLAccuracyAuthorization { get }
|
||||||
|
|
||||||
func requestAlwaysAuthorization()
|
func requestAlwaysAuthorization()
|
||||||
func startUpdatingLocation()
|
func startUpdatingLocation()
|
||||||
func stopUpdatingLocation()
|
func stopUpdatingLocation()
|
||||||
|
func startMonitoringSignificantLocationChanges()
|
||||||
|
func stopMonitoringSignificantLocationChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CLLocationManager: CLLocationManagerProtocol { }
|
extension CLLocationManager: CLLocationManagerProtocol { }
|
||||||
|
|||||||
@@ -9,11 +9,20 @@ import Combine
|
|||||||
import CoreLocation
|
import CoreLocation
|
||||||
|
|
||||||
class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationManagerDelegate {
|
class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationManagerDelegate {
|
||||||
|
enum LiveState {
|
||||||
|
case full
|
||||||
|
case limited
|
||||||
|
case off
|
||||||
|
}
|
||||||
|
|
||||||
private let clientProxy: ClientProxyProtocol
|
private let clientProxy: ClientProxyProtocol
|
||||||
private let locationManager: CLLocationManagerProtocol
|
private let locationManager: CLLocationManagerProtocol
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
|
|
||||||
private let authorizationStatusSubject: CurrentValueSubject<CLAuthorizationStatus, Never>
|
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.
|
/// Cached joined room proxies keyed by room ID, kept in sync with the active sessions dictionary.
|
||||||
private var activeRoomProxies = [String: JoinedRoomProxyProtocol]()
|
private var activeRoomProxies = [String: JoinedRoomProxyProtocol]()
|
||||||
@@ -23,9 +32,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
var authorizationStatus: CurrentValuePublisher<CLAuthorizationStatus, Never> {
|
private var liveState = LiveState.off
|
||||||
authorizationStatusSubject.asCurrentValuePublisher()
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
init(clientProxy: ClientProxyProtocol,
|
init(clientProxy: ClientProxyProtocol,
|
||||||
@@ -118,6 +125,19 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
stopAllSessions()
|
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)
|
authorizationStatusSubject.send(manager.authorizationStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,22 +221,30 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
|
|||||||
locationManager.distanceFilter = CLLocationDistance(minimumDistance)
|
locationManager.distanceFilter = CLLocationDistance(minimumDistance)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isUpdating = false
|
|
||||||
|
|
||||||
private func startUpdatingLocation() {
|
private func startUpdatingLocation() {
|
||||||
guard !isUpdating else { return }
|
guard liveState == .off else { return }
|
||||||
isUpdating = true
|
|
||||||
|
|
||||||
MXLog.info("Starting live location updates via delegate")
|
if locationManager.accuracyAuthorization == .fullAccuracy {
|
||||||
locationManager.startUpdatingLocation()
|
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() {
|
private func stopUpdatingLocation() {
|
||||||
guard isUpdating else { return }
|
if liveState == .full {
|
||||||
isUpdating = false
|
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")
|
liveState = .off
|
||||||
locationManager.stopUpdatingLocation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendLocationToActiveRooms(_ coordinate: CLLocationCoordinate2D) async {
|
private func sendLocationToActiveRooms(_ coordinate: CLLocationCoordinate2D) async {
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ final class LiveLocationManagerTests {
|
|||||||
#expect(roomProxy.startLiveLocationShareDurationCalled)
|
#expect(roomProxy.startLiveLocationShareDurationCalled)
|
||||||
#expect(!roomProxy.stopLiveLocationShareCalled)
|
#expect(!roomProxy.stopLiveLocationShareCalled)
|
||||||
#expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room:matrix.org"] != nil)
|
#expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room:matrix.org"] != nil)
|
||||||
|
#expect(locationManagerMock.startUpdatingLocationCalled)
|
||||||
|
#expect(!locationManagerMock.startMonitoringSignificantLocationChangesCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -131,6 +133,10 @@ final class LiveLocationManagerTests {
|
|||||||
|
|
||||||
#expect(roomProxy.stopLiveLocationShareCalled)
|
#expect(roomProxy.stopLiveLocationShareCalled)
|
||||||
#expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room:matrix.org"] == nil)
|
#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
|
@Test
|
||||||
@@ -156,6 +162,26 @@ final class LiveLocationManagerTests {
|
|||||||
#expect(appSettings.liveLocationSharingTimeoutDatesByRoomID["!room2:matrix.org"] != nil)
|
#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
|
// MARK: - Private
|
||||||
|
|
||||||
private func makeRoomProxy(roomID: String) -> JoinedRoomProxyMock {
|
private func makeRoomProxy(roomID: String) -> JoinedRoomProxyMock {
|
||||||
|
|||||||
Reference in New Issue
Block a user