Monitoring location updates on Android in a consistent fashion (e.g in a service, and while the screen is locked) can be quite a challenge. This article tackles the problem head-on
The code discussed in this article was designed using Delphi 10.2 Tokyo Release 2, however it should at least work on Berlin, and possibly Seattle. For those who want to make a fast start, go to the Kastri Free project and download it. The demo project for this article is in the Demos\AndroidLocation folder.
Developing an app with location updates is, on the face of it, fairly straightforward. You create a new Firemonkey app, put a TLocationSensor on the form, and away you go. However, if your intention is to track the device while the app is not even running, you need to use a service. Even if you’re tracking just using an app, when the screen is locked, the situation can change. This discussion focuses on the former problem, i.e. tracking the device when the app is not even running.
These are the main points that I’ll cover:
- Starting the service when the device is turned on, or “restarted”
- Monitoring the device for when the screen is locked/unlocked, and when entering/exiting “doze” mode
- Moving the service into the “foreground”, so that it still has network access even when the screen is locked
- Catering for “doze” mode
- Other techniques used in the demo
- Communicating with a server
Starting the service on boot or restart
If you have a service that tracks the device location, you might want it to start when the device is turned on, or when it is restarted (which can be two different things now), so that tracking starts immediately. You may have in the past seen articles that cover being able to start an application at boot, however what if the user does not necessarily want to see the app start when the device starts?
The answer is in some Java code I have devised that acts as a “multipurpose” broadcast receiver. The code is part of the Kastri Free project, in DWMultibroadcastReceiver.java. This code will respond when the device boots, and take action depending on which intent actions are being filtered, including starting the service, and monitoring alarms.
In order to be able to start the service at boot or restart, and to monitor for the “doze” alarm, there are a couple of entries made in the manifest, as per this image:
Monitoring for screen lock and “doze” mode
When the screen is locked, after a period of time on later versions of Android, the system restricts network access to processes that are running in the foreground. To make matters worse, on later versions of Android, if the device is locked for an extended amount of time (somewhere around an hour on my Nexus 5X running Android 8.1) it can enter what is called “doze” mode, where it may not receive location updates.
To monitor for when the screen is locked or unlocked, or enters or exits “doze” mode, the service needs to register a receiver to listen for broadcasts for those specific intent actions. In the demo code, this is handled by the TServiceReceiver class, which forwards the intent on to the service.
“Moving” the service into the foreground
To alleviate the network access problem when the screen is locked, the service needs to be “moved” into the foreground. When the service detects the screen is locked, it calls StartForeground, which puts it into the foreground state. Foreground services require that a notification is supplied, which means that a notification icon would normally appear (if the screen was unlocked) in the status bar. When the screen is unlocked, the service calls StopForeground, which puts it out of foreground state. Since the service is in the foreground only when the screen is locked, the user will notice the notification only when the lock screen is viewed:
Catering for “doze” mode
When in this state, the only way to keep posting regular updates (although the device will be stationary anyway) is by use of an alarm, set with the AlarmManager, and in a special “allow when idle” mode. When using an alarm in this mode, the documentation says that it should be set at no more than once per 9 minutes.
As above, the service monitors for changes in lock mode, and when entering lock mode, sets an alarm. When the alarm goes off, the multipurpose receiver (described in the first section) “starts” the service (although in this case it has already started), and in the AndroidServiceStartCommand event updates the location using getLastKnownLocation, and resets the alarm.
Other techniques used in the demo
If you study the code, you may notice that it does not use TLocationSensor. Part of the reason for this is that it needs an instance of the Android LocationManager anyway (JLocationManager in Delphi) in order to call getLastKnownLocation, and I wanted to make sure that I had absolute control over how the listeners were set up etc. It’s quite possible that the same result could be achieved by using TLocationSensor.
Also, you may notice the use of a timer (not an FMX TTimer, though), which is set to a 4 minute interval. This is a “hangover” from experimentation of location updates when the screen is locked, and when the device enters doze mode. I have left it in, in case it might be needed in the future.
Communicating with a server
The whole point of the demo is so that when a location update is available, it can be sent to a server of some kind. In this case, it’s geared towards sending a JSON request to (presumably) a REST server. If you use this part of the demo, you could modify the cLocationUpdateURL to one of your own, and modify cLocationRequestJSON to suit your own requirements.
If you’re just interested in experimenting with the demo and don’t have your own server, you may contact me, and I can set you up to access mine.
As always, instead of explaining a lot of the code, I will field questions if you have any.
I’d like to thank Atek Sudianto, from Jakarta, Indonesia who set me down this path. I am in the planning stages of an app that requires all of what I’ve covered in this article, and his enquiries gave me the “prod” I needed.