Android 14 Support
The Background Geolocation SDK for React Native v4.13.2, Cordova v4.13.2, Capacitor v5.1.2 and Flutter v4.12.2 now includes support forAndroid 14 (SDK 34)
Android continues to “turn the screws” with this release, with more limitations imposed on the background operation of foreground-services, but the Background Geolocation SDK follows the rules and continues to walk the path that Android clearly wants developers to follow.
AlarmManager
Exact Alarms
The Background Geolocation SDK has traditionally made use of “exact alarms” for many features, including Config.stopTimeout
, Config.motionTriggerDelay
, Config.schedule.
The permission making those “exact alarms” is now denied by default and the usage of permissions granting “exact alarms” will now be more closely scrutinized. For this reason, the SDK will now leave it up to developers to manually add a new permission to their own AndroidManifest
as a new step in the Setup Instructions linked at the Github repo.
<manifest>
<uses-permission android:minSdkVersion="34" android:name="android.permission.USE_EXACT_ALARM" />
.
.
.
</manifest>
If you choose to not add this new permission, the SDK will simply fall-back to using “in-exact alarms”, so an alarm scheduled to occur 5 minutes in the future, might fire 7–8 minutes instead. This is not a big deal for operations such as Config.stopTimeout
.
However, one major advantage of “exact alarms” is their ability to launch a foreground-service in the background. The SDK had previously used this to bypass foreground-launch restrictions imposed by Android 13. For example, say you had a BackgroundFetch
callback which calls .getCurrentPosition(options)
while your app was in the background:
BackgroundFetch.configure({
minimumFetchInterval: 15
}, async (taskId) => {
try {
const location = await BackgroundGeolocation.getCurrentPosition({
samples: 3
});
console.log('[getCurrentPosition] success: ', location);
} catch (error) {
console.warn('[getCurrentPosition] ERROR: ', error);
}
});
Executing the code above with your app in the background and the SDK in the stationary state (ie: where not foreground-service is currently running) would result in the following error shown in logcat
:
ActivityManager startForegroundService() not allowed due to mAllowStartForeground false
The SDK would catch
this error and re-attempt a foreground-service launch by first firing an AlarmManager
exact-alarm with an interval of 1ms
, (exact-alarms are granted an exception to foreground-service background launch rules), whose receiver would then re-launch the failed foreground-service Intent
and voila: it worked! But in Android 14, this clever trick will no longer work without manually adding the permission above.
There is still one more clever trick up my sleeve, but it won’t work all-the-time like AlarmManager
exact-alarms. Sometimes, it will simply be impossible to call .getCurrentPosition
while your app is in the background with the plugin in the stationary state.
A new Location
attribute: Location.Coords.age
This new attribute has been added to both Android and iOS. Since sometimes your .getCurrentPosition
request will not always be able to launch a foreground-service to turn location-services on to request a fresh location, .getCurrentPosition
will always at least return the last known location. Because this location can be from some time ago, the plugin now attaches a new attribute Location.Coords.age (milliseconds)
, showing how old the location’s timestamp
was relative to the OS SystemTime at the moment it was received.
Above are two locations recorded from simulated BackgroundFetch
events, just like the code above, where the app was in the background.
recorded_at
: fromLocation.timestamp
recorded on the device.created_at
: the time the location was received at the server.
The first record in the table above shows an AGE
of 49601ms
. Playing a little with the raw UTC timestamp data from the table above, we can see that recorded_at + age =~ created_at
:
>recorded_at = new Date('2023-08-30T20:04:42.669Z')
>Wed Aug 30 2023 16:04:42 GMT-0400 (Eastern Daylight Time)
>created_at = new Date('2023-08-30T20:05:30.131Z')
>Wed Aug 30 2023 16:05:30 GMT-0400 (Eastern Daylight Time)
>age = 49601
>49601
>recorded_at_plus_age = recorded_at.getTime() + age
>1693425932270
>new Date(recorded_at_plus_age)
>Wed Aug 30 2023 16:05:32 GMT-0400 (Eastern Daylight Time)