Files
bundesmessenger-ios/Riot/Modules/LocationSharing/UserLocationService.swift
T
JanNiklas Grabowski b298dedc22 chore: update from foss 1.11.19 (MESSENGER-6656)
Merge commit 'f823ab9aae70e8d15ed7cc079210dd9bbbb6c8e1' into feature/foss_update_1_11_19

* commit 'f823ab9aae70e8d15ed7cc079210dd9bbbb6c8e1':
  finish version++
  version++
  comments
  update submodule
  remove obsolete tests
  removed unused code
  update submodule
  fix
  Libolm removal
  update license macro
  update license
  Prepare for new sprint

# Conflicts:
#	Config/AppVersion.xcconfig
#	IDETemplateMacros.plist
#	LICENSE
#	README.md
#	Riot/Categories/MXSession+Riot.m
#	Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift
#	Riot/Managers/KeyValueStorage/Extensions/Keychain.swift
#	Riot/Managers/KeyValueStorage/KeyValueStore.swift
#	Riot/Managers/KeyValueStorage/KeychainStore.swift
#	Riot/Managers/KeyValueStorage/MemoryStore.swift
#	Riot/Managers/PushNotification/PushNotificationService.m
#	Riot/Managers/Settings/RiotSettings.swift
#	Riot/Managers/Settings/Shared/RiotSharedSettings.swift
#	Riot/Modules/Analytics/AnalyticsUIElement.swift
#	Riot/Modules/Application/AppCoordinator.swift
#	Riot/Modules/Application/LegacyAppDelegate.h
#	Riot/Modules/Application/LegacyAppDelegate.m
#	Riot/Modules/Authentication/Legacy/AuthenticationViewController.h
#	Riot/Modules/Authentication/Legacy/AuthenticationViewController.m
#	Riot/Modules/Authentication/Legacy/Views/AuthInputsView.h
#	Riot/Modules/Authentication/Legacy/Views/AuthInputsView.m
#	Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m
#	Riot/Modules/Common/Recents/RecentsViewController.m
#	Riot/Modules/Common/WebViewController/WebViewViewController.m
#	Riot/Modules/Contacts/Details/ContactDetailsViewController.m
#	Riot/Modules/Contacts/Views/ContactTableViewCell.m
#	Riot/Modules/Favorites/FavouritesViewController.h
#	Riot/Modules/Favorites/FavouritesViewController.m
#	Riot/Modules/GlobalSearch/UnifiedSearchViewController.m
#	Riot/Modules/People/PeopleViewController.h
#	Riot/Modules/People/PeopleViewController.m
#	Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift
#	Riot/Modules/Room/DataSources/RoomDataSource.m
#	Riot/Modules/Room/Files/RoomFilesViewController.m
#	Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m
#	Riot/Modules/Room/Members/RoomParticipantsViewController.m
#	Riot/Modules/Room/RoomViewController.m
#	Riot/Modules/Room/Settings/RoomSettingsViewController.m
#	Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroCell.swift
#	Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroCellContentView.swift
#	Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroViewData.swift
#	Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h
#	Riot/Modules/Rooms/RoomsViewController.h
#	Riot/Modules/Rooms/ShowDirectory/Cells/Network/DirectoryNetworkTableHeaderFooterView.swift
#	Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift
#	Riot/Modules/Rooms/ShowDirectory/PublicRoomsDirectoryViewModel.swift
#	Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift
#	Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift
#	Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift
#	Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift
#	Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift
#	Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift
#	Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModel.swift
#	Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModelType.swift
#	Riot/Modules/SetPinCode/PinCodePreferences.swift
#	Riot/Modules/SetPinCode/SetupBiometrics/BiometricsAuthenticationPresenter.swift
#	Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m
#	Riot/Modules/Settings/Security/SecurityViewController.m
#	Riot/Modules/Settings/SettingsViewController.m
#	Riot/Modules/SplitView/SplitViewCoordinator.swift
#	Riot/Modules/SplitView/SplitViewCoordinatorType.swift
#	Riot/Modules/StartChat/StartChatViewController.m
#	Riot/Modules/TabBar/MasterTabBarController.h
#	Riot/Modules/TabBar/MasterTabBarController.m
#	Riot/Utils/EventFormatter.m
#	Riot/Utils/HTMLFormatter.swift
#	Riot/Utils/Tools.m
#	RiotNSE/NotificationService.swift
2024-10-18 15:45:54 +02:00

278 lines
9.5 KiB
Swift

//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Foundation
import CoreLocation
import MatrixSDK
/// UserLocationService handles live location sharing for the current user
class UserLocationService: UserLocationServiceProtocol {
// MARK: - Constants
private enum Constants {
/// Minimum delay in milliseconds to send consecutive location for a beacon info
static let beaconSendMinInterval: UInt64 = 5000 // 5s
/// Delay to check for experied beacons
static let beaconExpiredVerificationInterval: TimeInterval = 5 // 5s
}
// MARK: - Properties
// MARK: Private
private let locationManager: LocationManager
private let session: MXSession
/// All active beacon info summaries that belongs to this device
/// Do not update location for beacon info started on another device
private var deviceBeaconInfoSummaries: [MXBeaconInfoSummaryProtocol] = []
private var beaconInfoSummaryListener: Any?
private var expiredBeaconVerificationTimer: Timer?
// MARK: Public
// MARK: - Setup
init(session: MXSession) {
// (bwi #4549): its ok to only test the buildsetting here. Wellknown check is handled in UI
self.locationManager = LocationManager(accuracy: .full, allowsBackgroundLocationUpdates: BWIBuildSettings.shared.locationSharingEnabled)
self.session = session
}
// MARK: - Public
func requestAuthorization(_ handler: @escaping LocationAuthorizationHandler) {
self.locationManager.requestAuthorization(handler)
}
func start() {
self.locationManager.delegate = self
// Check for existing beacon info summaries for the current device and start location tracking if needed
self.setupDeviceBeaconSummaries()
self.startListeningBeaconInfoSummaries()
self.startVerifyingExpiredBeaconInfoSummaries()
}
func stop() {
self.stopLocationTracking()
self.stopListeningBeaconInfoSummaries()
self.stopVerifyingExpiredBeaconInfoSummaries()
}
// MARK: - Private
// MARK: Beacon info summary
private func startVerifyingExpiredBeaconInfoSummaries() {
let timer = Timer.scheduledTimer(withTimeInterval: Constants.beaconExpiredVerificationInterval, repeats: true) { [weak self] _ in
self?.verifyExpiredBeaconInfoSummaries()
}
self.expiredBeaconVerificationTimer = timer
}
private func stopVerifyingExpiredBeaconInfoSummaries() {
self.expiredBeaconVerificationTimer?.invalidate()
self.expiredBeaconVerificationTimer = nil
}
private func verifyExpiredBeaconInfoSummaries() {
for beaconInfoSummary in deviceBeaconInfoSummaries where beaconInfoSummary.isActive == false && beaconInfoSummary.hasStopped == false {
// TODO: Prevent to stop several times
// Wait for isStopping status
self.session.locationService.stopUserLocationSharing(withBeaconInfoEventId: beaconInfoSummary.id, roomId: beaconInfoSummary.roomId) { response in
}
}
// Remove non active beacon info summaries
self.deviceBeaconInfoSummaries = self.deviceBeaconInfoSummaries.filter({ beaconInfoSummary in
return beaconInfoSummary.isActive
})
}
private func getExistingDeviceBeaconSummaries() -> [MXBeaconInfoSummaryProtocol] {
guard let userId = self.session.myUserId else {
return []
}
return self.session.locationService.getBeaconInfoSummaries(for: userId).filter { summary in
return self.isDeviceBeaconInfoSummary(summary) && summary.isActive
}
}
private func setupDeviceBeaconSummaries() {
let existingDeviceBeaconInfoSummaries = self.getExistingDeviceBeaconSummaries()
self.deviceBeaconInfoSummaries = existingDeviceBeaconInfoSummaries
self.updateLocationTrackingIfNeeded()
for summary in existingDeviceBeaconInfoSummaries {
self.didReceiveDeviceNewBeaconInfoSummary(summary)
}
}
private func startListeningBeaconInfoSummaries() {
let beaconInfoSummaryListener = self.session.aggregations.beaconAggregations.listenToBeaconInfoSummaryUpdate { [weak self] roomId, beaconInfoSummary in
self?.didReceiveBeaconInfoSummary(beaconInfoSummary)
}
self.beaconInfoSummaryListener = beaconInfoSummaryListener
}
private func stopListeningBeaconInfoSummaries() {
if let listener = self.beaconInfoSummaryListener {
self.session.aggregations.beaconAggregations.removeListener(listener)
}
}
private func isDeviceBeaconInfoSummary(_ beaconInfoSummary: MXBeaconInfoSummaryProtocol) -> Bool {
return beaconInfoSummary.userId == self.session.myUserId && beaconInfoSummary.deviceId == self.session.myDeviceId
}
private func didReceiveBeaconInfoSummary(_ beaconInfoSummary: MXBeaconInfoSummaryProtocol) {
guard self.isDeviceBeaconInfoSummary(beaconInfoSummary) else {
return
}
let existingIndex = self.deviceBeaconInfoSummaries.firstIndex(where: { beaconInfoSum in
beaconInfoSum.id == beaconInfoSummary.id
})
if beaconInfoSummary.isActive {
if let index = existingIndex {
self.deviceBeaconInfoSummaries[index] = beaconInfoSummary
} else {
self.deviceBeaconInfoSummaries.append(beaconInfoSummary)
// Send location if possible to a new beacon info summary
self.didReceiveDeviceNewBeaconInfoSummary(beaconInfoSummary)
}
} else {
if let index = existingIndex {
self.deviceBeaconInfoSummaries.remove(at: index)
}
}
self.updateLocationTrackingIfNeeded()
}
private func didReceiveDeviceNewBeaconInfoSummary(_ beaconInfoSummary: MXBeaconInfoSummaryProtocol) {
guard let lastLocation = self.locationManager.lastLocation else {
return
}
self.sendLocation(lastLocation, for: beaconInfoSummary)
}
// MARK: Location sending
private func sendLocation(_ location: CLLocation, for beaconInfoSummary: MXBeaconInfoSummaryProtocol) {
guard self.canSendBeaconRequest(for: beaconInfoSummary) else {
return
}
var localEcho: MXEvent?
self.session.locationService.sendLocation(withBeaconInfoEventId: beaconInfoSummary.id,
latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
inRoomWithId: beaconInfoSummary.roomId,
localEcho: &localEcho) { response in
switch response {
case .success:
break
case .failure(let error):
MXLog.error("Fail to send location", context: error)
}
}
}
private func didReceiveLocation(_ location: CLLocation) {
for deviceBaconInfoSummary in deviceBeaconInfoSummaries {
self.sendLocation(location, for: deviceBaconInfoSummary)
}
}
private func canSendBeaconRequest(for beaconInfoSummary: MXBeaconInfoSummaryProtocol) -> Bool {
// Check if location manager is started
guard self.locationManager.isUpdatingLocation else {
return false
}
let canSendBeaconRequest: Bool
if let lastBeaconTimestamp = beaconInfoSummary.lastBeacon?.timestamp {
let currentTimestamp = Date().timeIntervalSince1970 * 1000
canSendBeaconRequest = UInt64(currentTimestamp) - lastBeaconTimestamp >= Constants.beaconSendMinInterval
} else {
// The beacon info summary have no last beacon, we can send a request immediatly
canSendBeaconRequest = true
}
return canSendBeaconRequest
}
// MARK: Device location
private func stopLocationTracking() {
self.locationManager.stop()
self.locationManager.delegate = nil
}
private func updateLocationTrackingIfNeeded() {
if self.deviceBeaconInfoSummaries.isEmpty {
// Stop location tracking if there is no active beacon info summaries
if self.locationManager.isUpdatingLocation {
self.locationManager.stop()
}
} else {
// Start location tracking if there is beacon info summaries and location tracking is stopped
if self.locationManager.isUpdatingLocation == false {
self.locationManager.start()
}
}
}
}
// MARK: - LocationManagerDelegate
extension UserLocationService: LocationManagerDelegate {
func locationManager(_ manager: LocationManager, didUpdateLocation location: CLLocation) {
self.didReceiveLocation(location)
}
}