Skip to content

Commit

Permalink
Try to reduce time taken to get a trackable online
Browse files Browse the repository at this point in the history
I noticed that on a stationary device, or on a simulator set to simulate
a static location, the first trackable added would come online almost
immediately, but subsequent ones would not come online or sometimes
would come online after a long delay.

This is because a trackable will not come online until, after the
trackable is added, we receive a location update from LocationService.
The frequency of location updates from PassiveLocationManager is
determined by that of CLLocationManager, which usually only emits
location updates when the device has moved a sufficient distance (where
“sufficient” is determined by the publisher’s resolution policy).

So, we provide a mechanism for the publisher to request a location
update from CLLocationManager independently of the device’s motion, and
we request a location update when any trackable (except for the first)
is added.

It’s probably worth also explaining how Android behaves in this scenario
and why. My notes here are based on the codebase at commit 3a0d833.

There are two different things that happen in the Android asset tracking
SDK, either of which has the side effect of causing the publisher to
receive a location update when a new trackable is added:

1. The publisher calls DefaultMapbox.changeResolution, which in turn
   ends up removing and re-adding all of (Google SDK)
   FusedLocationProviderClient’s location observers (via its
   requestLocationUpdates method). The documentation for
   FusedLocationProviderClient.requestLocationUpdates implies that
   observers will always receive a location update after this method is
   called (although it might be an old location).

2. (Only in the case where the added trackable is set as the active
   trackable – i.e. when it is added using .track and not .add)

   The publisher calls DefaultMapbox.clearRoute, which calls (Mapbox
   SDK) MapboxNavigation.setNavigationRoutes. Through some process that I
   haven’t looked into, this ends up calling (Mapbox SDK)
   MapboxTripSession.kt’s navigatorObserver’s onStatus, which makes an
   explicit call to updateLocationMatcherResult, which emits a location
   update.

I think that the solution I’ve implemented for iOS turns Android side
effect 1 into an explicit behaviour.

Closes #425.
  • Loading branch information
lawrence-forooghian committed Dec 15, 2022
1 parent 476ba0c commit a73e823
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 1 deletion.
8 changes: 7 additions & 1 deletion Sources/AblyAssetTrackingPublisher/DefaultPublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,18 @@ extension DefaultPublisher {
ablyPublisher.subscribeForChannelStateChange(trackable: event.trackable)

trackables.insert(event.trackable)
//Start updating location only after the first trackable
if (trackables.count == 1){
//Start updating location only after the first trackable
locationService.startRecordingLocation()
locationService.startUpdatingLocation()
} else {
// We do this to increase the chances of receiving an enhanced location update for this trackable, so that the trackable can move into the online connection state as quickly as possible (see the hasSentAtLeastOneLocation check inside handleConnectionStateChange).

// If we did not do this, then (since startUpdatingLocation has already been called on the location service) in the case of a slowly-moving or stationary device, the trackable would not move into the online connection state until the device had moved a distance deemed worthy (by the resolution policy) of a new location update.
locationService.requestLocationUpdate()
}
resolveResolution(trackable: event.trackable)

hooks.trackables?.onTrackableAdded(trackable: event.trackable)
event.completion.handleSuccess()
duplicateTrackableGuard.finishAddingTrackableWithId(event.trackable.id, result: .success)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ class DefaultLocationService: LocationService {
locationManager.systemLocationManager.stopUpdatingLocation()
}

func requestLocationUpdate() {
/*
From reading the ``CLLocationManager/startUpdatingLocation`` documentation, this seems to be the only programmatic way to provoke it into emitting a location event:

> Calling this method several times in succession does not automatically result in new events being generated. Calling stopUpdatingLocation() in between, however, does cause a new initial event to be sent the next time you call this method.
*/
logHandler?.debug(message: "Received requestLocationUpdate", error: nil)
locationManager.systemLocationManager.stopUpdatingLocation()
locationManager.systemLocationManager.startUpdatingLocation()
}

enum LocationHistoryTemporaryStorageConfiguration {
private static let fileManager = FileManager.default

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ protocol LocationService: AnyObject {
func startRecordingLocation()
func stopRecordingLocation(completion: @escaping ResultHandler<LocationRecordingResult?>)
func changeLocationEngineResolution(resolution: Resolution)
/// Requests that the location service emit a new location update as soon as possible. The location service will make a best effort to ensure that, shortly after this method is called, its delegate receives a ``LocationServiceDelegate/locationService(sender:didUpdateRawLocationUpdate)`` and ``LocationServiceDelegate/locationService(sender:didUpdateEnhancedLocationUpdate)`` event.
func requestLocationUpdate()
}
33 changes: 33 additions & 0 deletions Tests/PublisherTests/DefaultPublisher/DefaultPublisherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,39 @@ class DefaultPublisherTests: XCTestCase {
XCTAssertEqual(publisher.activeTrackable, trackable)
}

func testAddSecondTrackable_callsRequestLocationUpdateOnLocationService() {
ablyPublisher.connectCompletionHandler = { completion in completion?(.success) }
let expectation = XCTestExpectation()
expectation.expectedFulfillmentCount = 2

// When tracking a trackable
publisher.add(trackable: trackable) { result in
switch result {
case .success:
expectation.fulfill()
case .failure:
XCTFail("Failure callback shouldn't be called")
}
}

// It should not yet have called requestLocationUpdate on the location service
XCTAssertFalse(locationService.requestLocationUpdateCalled)

// And then adding another trackable
publisher.add(trackable: Trackable(id: "TestAddedTrackableId1")) { result in
switch result {
case .success:
expectation.fulfill()
case .failure:
XCTFail("Failure callback shouldn't be called")
}
}
wait(for: [expectation], timeout: 5.0)

// It should call requestLocationUpdate on the location service
XCTAssertTrue(locationService.requestLocationUpdateCalled)
}

func testAdd_track_error() {
let errorInformation = ErrorInformation(type: .publisherError(errorMessage: "Test AblyPublisherService error"))
ablyPublisher.connectCompletionHandler = { completion in completion?(.failure(errorInformation)) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,9 @@ public class MockLocationService: LocationService {
stopRecordingLocationParamCompletion = completion
stopRecordingLocationCallback?(completion)
}

public var requestLocationUpdateCalled = false
public func requestLocationUpdate() {
requestLocationUpdateCalled = true
}
}

0 comments on commit a73e823

Please sign in to comment.