Previously, I have written two articles on monitoring location changes: one for iOS, and one for Android. This article and the related demo brings everything up-to-date and merges support for both operating systems.
STTP (Straight To The Point – I’m coining an acronym to replace TLDR)
If you’re just after the demo, you may find it here. As with most demos in the articles in this blog, it is dependent on the Kastri library. Please be aware that for iOS you will need to apply the System.iOS.Sensors patch as detailed below. Delphi 10.4 users: An update for the demo is coming soon, as it will not require the patch for iOS.
Requirements
As per the previous articles, the aim is to be able to monitor location changes reliably, including when the application is not running. Due to restrictions introduced in later versions of Android, and some shortcomings in the iOS implementation in Delphi, there some “hoops to jump through” to make this all happen.
Project Groups
The demo has 2 project groups: one containing the application and service (CPLAndroidGroup), and one that contains just the application (CPLAppOnlyGroup). CPLAppOnlyGroup is useful for compiling for iOS, because if you compile just the application in the CPLAndroidGroup for iOS, the IDE will still compile the service, regardless of the fact that you are not compiling the application for Android. This issue has been reported, here.
Grijjy Cloud Logger
You may notice in the code that use is made of the excellent Grijjy Cloud Logger framework. This has been useful for debugging so I have left the code in, and if you want to use it, you will need to add defines (CLOUDLOGGING) in the Project Options for the supported platform targets.
Unfortunately at present there is no support for Grijjy Cloud Logging on Android 64-bit. Hopefully this will be rectified soon.
If you choose to use Grijjy Cloud Logging, you will need to either define user override environment variables in the IDE options that correspond to the library locations:
- $(Grijjy) refers to Grijjy Foundation
- $(ZeroMQ) refers to ZeroMQ for Delphi
..or modify the project search paths to point to the required libraries.
You will also need to modify the value for cCloudLoggingHost in the CPL.Consts unit in the Common folder, to point to your cloud logging broker instance.
Application
The application has been refactored to integrate the iOS implementation to make things work smoother, and hopefully be easier to follow. Notably, it now includes the DW.ServiceCommander.Android unit, which contains:
TServiceCommander
This class:
- Handles the BecameActive and EnteredBackground application events, sending a local broadcast to the service to inform it of the change so that the service can enter or exit foreground mode.
- Has a StartService command that starts the service if it is not already running.
- Has a SendCommand method to send any other commands that the service needs to receive.
DW.Location unit
This unit and the units it uses contain the classes necessary for integrating iOS support, and for Android for receiving location messages from the service. The iOS implementation uses TLocationSensor which comes with Delphi, however there are some customizations that are necessary, which are detailed later in this article.
Android Service
On Android, the application needs a service, and since it needs to be able to track location when the app is not running, it needs to start when the device starts. As per the previous Android demo, instead of using TLocationSensor, I chose to write custom code; mostly for more control, and also so that it can be easily “swapped out” for another implementation such as one that uses FusedLocationProviderClient.
The ServiceModule has been refactored to decouple some of the functionality, namely:
TBackgroundMonitor
The OnScreenLockChange event is fired when the screen is locked.
When the device enters “doze” mode, an alarm is set so that the service will be invoked when the alarm fires, and the OnDozeChange event fires so that any additional action may be taken.
TLocationUpdater
This class is responsible for processing the location update. It may send the location details to a server, and/or store the location in a local database.
TLocation
This class existed in the prior version of the demo, however it has been refactored somewhat to improve reliability.
Starting the service at boot time
The service starts when the device starts by using a broadcast receiver (A Java class called DWMultiBroadcastReceiver, in dw-kastri-base.jar) which is configured in AndroidManifest.template.xml
The android.intent.action.BOOT_COMPLETED and android.intent.action.QUICKBOOT_POWERON intent filters ensure that the broadcast receiver receives these intents on cold boot and restart. In the Java code for DWMultiBroadcastReceiver, the metadata is checked for an entry named DWMultiBroadcastReceiver.KEY_START_SERVICE_ON_BOOT, and the metadata value is used as the name of the service to start:
Note that DWMultiBroadcastReceiver also exists in dw-multireceiver.jar, however I am in the process of merging Java code used in Kastri in an effort to reduce the number of jar files.
Considerations for later versions of Android
In Android 10, using location services via a service requires requesting a runtime permission of android.permission.ACCESS_BACKGROUND_LOCATION, so this permission is added to AndroidManifest.template.xml (the permission cannot be configured via the IDE), and the permission is requested in code.
From Android 8, a service is required to start in foreground mode if the application goes to the background or is not running. The previous demo went some way to solving this, however it was not 100% reliable; this demo should work as it should. Since the demo uses a regular service, when starting in the foreground the service needs to post a notification which is visible in the status bar, and a banner is visible in the lock screen. Although this is a tad intrusive, presently it’s the only option using a plain Service constructed in Delphi, as using an Intent Service is currently broken in Delphi. Note: This issue has now been fixed in Delphi 10.4, so this demo may be updated to use an IntentService.
iOS
Delphi implementation issues
Note: the issues described here have been fixed in Delphi 10.4. An update to the demo for Delphi 10.4 users is coming soon…
As per the previous article about location monitoring on iOS, the main issue is that using TLocationSensor “out of the box”, location updates do not occur when the application is in the background or the application is not running. The easiest way to solve this was to patch the System.iOS.Sensors unit, which uses code from the DW.iOS.Sensors unit to help manage the usage (e.g. When In Use and Always), background status (normal and significant changes) and activity (such as Auto and Fitness).
Applying the System.iOS.Sensors unit patch
The easiest way to apply the patch is with Codex, using the Source Patcher function:
If you have not already installed the Patch executable, there is a download link in the Source Patcher dialog:
After downloading/installing, you will need to change the name of the executable to something other than patch.exe (such as p.exe), as Windows thinks it is some kind of installer and will not run without elevated privileges.
Select the patch executable, source file, patch file, and destination folder, e.g:
The System.iOS.Sensors.10.3.3.patch file is supplied with the demo in the Patches folder
The patched code in the resulting file is indicated by comments beginning with // DW, so they’re easy to find.
Testing the demo
I would like to have done more testing on this demo than I have (so far), however it can be rather time consuming, since some of the “live” testing requires physically moving from place to place. I’d like to once again thank Atek Sudianto from Jakarta, for running this demo through its paces.
Android
Normally a device will not go into “doze” mode until a couple of hours of inactivity, when not connected to any charging source. This can make it difficult to debug since debugging normally requires the device to be plugged in to the computer doing the debugging, however I stumbled across a technique for “faking” doze mode. Long story short: it’s via an adb command, and I have now integrated this function into Device Lens:
As pictured, click the “minus” button to fake doze mode, and the “plus” button to restore it to normal.
To test the service starting at boot, either turn the device off, then back on again, or use the device’s reset facility. Shortly after the device starts, you should see the notification icon for the app (default is the Firemonkey icon) in the status bar. This is because the service has started in foreground mode, which requires a notification being displayed.
If you then start the application, you should see the notification icon disappear, because the service has exited foreground mode.
iOS
Location updates on iOS when the app is in the background or not running have been exceedingly frustrating (regardless of the issues in Delphi) ever since I’ve started using them. When first testing this new demo, I found that it did not appear to be generating updates, despite the device having moved significantly (more than 500 metres, apparently), then after a while (a day or so), location updates started happening (there were no code changes), without the device moving significantly.
I’d be interested to hear from others about their experiences with background location updates on iOS (using their own code or the demo from this article), so please feel free to leave a comment.
Looking to the future
I have been working on an implementation of location updates that uses FusedLocationProviderClient, however it has been left out for now due to time constraints.
I am also looking into solving the intrusive notification that shows when the service is in the foreground on Android. Indications are that a JobIntentService should be used, however as described earlier this is currently broken in Delphi, though I am looking into solving that issue, as well as investigation other methods.
Finally, hopefully the location services issues for iOS in Delphi will be solved in Delphi 10.4, removing the need to patch the Delphi source.
Hello Dave.
Thank you for your amazing work (can’t imagine to solve several issues in Delphi without your help).
I just upgraded to delphi 10.3.3, downloaded new kastri library (which is fabulous btw.) and latest location monitoring application but I’m getting an error “Project LocationApp.apk raised exception dass EJNIException with message
Java.lang.NullPointerException:Attempt to invoke virtual method ‘java.lang.Classloader java.lang.Class.getclassloader()’ on a null object reference’.” raised on the row FTimerTaskDelegate := TTimerTaskDelegate.Create(Self); in unit DW.TimerTask.Android.pas.
I’m completely lost here – any idea what can be the problem please?
I can run the demo by commenting the lines for creating the timers, but as they are important it’s not a solution…
Sorry, Mark.. I have only just come across this comment. Is this a new project you’re creating, or is it the demo? If it is your own project, please ensure that you add dw-kastri-base.jar (from the Lib folder in Kastri Free) to the Libraries node of the Android target in Project Manager.
Dear Dave, thank you very much for your reply.
I had problem with running the demo (location monitoring application) and my own project as well.
In the demo there was also not included “dw-kastri-base.jar” file, but there was “dw-multireceiver.jar” instead. After your reply I included “dw-kastri-base.jar” file into the Libraries, but I was still getting and error (a new one) till I didn’t remove “dw-multireceiver.jar” from the Libraries.
Anyhow now the demo it’s working great. Hope this will also help others facing the same issue.
Thank you for your time.
Hi Dave,
very nice peace of code, thank you for this.
I tried it with Rio/Android 8 (Galaxy S7). It basically works but sometimes I have the same issue than Martijn:
1) Putting App to background
2) Lock screen
3) unlocking screen after some minutes (only sometimes, not reprodusable so far) -> App is frozen
4) Android shows “LocationApp isn’t responding”.”
I added some logging for this. After restarting I can see that the App and the Service has been frozen at that point.
Last steps in App (always when this happens):
ApplicationEventMessageHandler:2
ApplicationEventMessageHandler:3
SendCommand
After that SendCommand the DoStatus/SERVICE_STATUS is generatet in service but never received from app?! Any ideas?
Futhermore, wouldn’t it be possible to have that service standalone (only foreground because network in sleep mode, just starting/restarting from App, no communication between app and service)? In that case the app would still work when the service freezes or maybe the app could even kill/restart the service?!
Hi Michael,
Is this happening only on Android 8? I’ll have to try it on my Android 8 device, however I’m away from it until next week.
It could be that for some reason the service is unable to tell if the app has shut down (and therefore need to run as foreground). I’d rather not have it running as foreground all the time, since the notification could be considered intrusive. Long-term I have been planning to change the service into a JobIntentService however that requires a bug fix on Embarcadero’s part.
I did some more testing and figured out that in my case TAndroidHelperEx.KeyguardManager.
inKeyguardRestrictedInputMode always returns false. And that seems to be because settings->security->screenlock->none. I usually develop without screenlock and inKeyguardRestrictedInputMode seems to have different behavior then.
So as a workaround I did:
if intent.getAction.Equals(TJIntent.JavaClass.ACTION_SCREEN_OFF) then
ScreenLockChange(True)
else
ScreenLockChange(TAndroidHelperEx.KeyguardManager.
inKeyguardRestrictedInputMode);
After that the demo seems to work as expected.
But maybe you have a more elegant/secure way of solving this?!
Thanks again for your great work!
Hi Michael, your solution is pretty elegant, and thanks!
Hello David, there is a more practical way to send the location of the device. I did it myself with a BroadcastReceiver (a java class) that broadcast starts the service after receiving a Push notification. But for that I don’t use Embarcadero’s standard notification. My broadcast receives the notification and depending on the code I send in the Push, it can start a service or even start an activity.
The big advantage is that if you are using this method with an FCM connection, the same mode doze not make any difference. A high-priority push will wake up your cell phone and modems. I simulated and use this option.
With the following commands in the adb shell I tested it:
$ adb shell dumpsys deviceidle force-idle
$ adb shell dumpsys battery unplug
$ adb shell am set-inactive true
More details at (https://developer.android.com/training/monitoring-device-state/doze-standby)
To send the push I created a desktop application with TTask that targets push sending, because FCM can only send 1000 notifications at a time.
Best
Nice! Thanks for the tips. Sorry for the delay in replying – the notifications for comments on the website were going to spam 🙁
Hi Dave,
in AndroidManifest.template.xml android:targetSdkVersion is set as follows:
Application:”26″
Service:”%targetSdkVersion%”
So when deploying App on Android 9 (API 28) this results in (AndroidManifest.xml)
Application:”26″
Service:”28″
The Problem is when running API 26 on Android 9 there still seem to be some Delphi bugs/warnings, e.g. “Detected problems with API compatibility (visit g.go/dev/appcompat for more info)”
So when changing the application also to API 28 the demo stops/freezes as soon as the permission is checked?!
Do you have an idea what could be the reason here?
(using 10.3.3)
Installed on IOS okay.
On Android error:
[Error Error] Specified platform sdk not found: ‘C:\Users\User\AppData\Roaming\Embarcadero\BDS\20.0\AndroidSDK25.2.5_64bit.sdk’
I edited the manifest to:
Cheers
Does that folder exist? You might want to check the Android SDK/NDK settings in the Delphi SDK Manager
To what?
Sorry missed it.
manifest to:
Delphi Android SDK points to :
C:\Program Files\Unity\Hub\Editor\2019.3.1f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platforms\android-29
That folder does not exist. Where is that being picked from and where can I change it ?
I assume you mean the SDK API-level location value? There’s a number of settings for Android SDK. If there’s an android-28 folder, make it that.
Change the manifest to uses-sdk android:minSdkVersion=”26″ android:targetSdkVersion=”29″
It doesn’t post with the greater than chars
If I change the SDK API level location to eith 26 -28 – 29. I get the same error:
[Error Error] Specified platform sdk not found: ‘C:\Users\User\AppData\Roaming\Embarcadero\BDS\20.0\AndroidSDK25.2.5_64bit.sdk’
Does that folder actually exist? If so, does it have any subfolders? There should be at least: build-tools, platforms, platform-tools and tools.
Earlier you quoted: C:\Program Files\Unity\Hub\Editor\2019.3.1f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platforms\android-29
Which is very different to the other one. You should establish where exactly the SDK is that you want to use, and correctly configure the Android SDK settings in the Delphi SDK Manager
The unity path was already there when I installed Delphi.
I have not had an issue with any other of the demos working on Android.
Or any new apps I create.
Is the path hard wired somewhere or the SDK to use ?
[…] This article, and the demo for it, has been superceded by this article and the demo associated with it. Please refer to that, instead of this […]
Hi Dave,
Your demo helped me a lot, thanks for the job.
Could you tell me what would be the best way to get the speed, course and altitude to be used in TServiceModule.LocationChangeHandler?
Hi Dave,
Thank you for this awesome demo.
I made the code working using last previous version of your demo but now with this new update I have a problem in android 9 service unable to start foreground.cause the service to crash, any fixed for this error. By the way I’m using Rio 10.3.3. Thanks in advance for your help
Regards,
Gerry
Hi Gerry,
Is this in the actual demo itself? Note that I recently checked in AndroidManifest.template.xml, which had been omitted.
Hi Dave,
Thank you for the response. The startForeground problem was fixed. All are working well after updating the manifest.
But suddenly this morning I got this [DCC Fatal Error] MainUnit.pas(50): F2613 Unit ‘DW.MultiReceiver.Android’ not found. when I compile the project, I already added the dw-kastri-base.jar and removed the dw-multireciever.jar from the Libraries.
I used the SDK API-Level Location C:\Users\Public\Documents\Embarcadero\Studio\20.0\PlatformSDKs\android-sdk-windows\platforms\android-28.
I also used Rio 10.3.3
Thanks in advance for the response.
Regards,
Gerry
The error indicates that it cannot find a unit it needs to compile, so you may have an issue with the search paths in the Project Options, or you have inadvertently deleted the file. The file in question is located in the Core folder of the Kastri repo (please migrate to using Kastri if you are currently using Kastri Free)
Hello Dave!
My app now is working well with this gps location being monitored, But I have a problem on how to stop the service when I logout or exit the host app? When I closed the host app the service still sending notification.
thanks in advance.
Reagrds,
Gerry
If you want to stop location notifications from the app, you can use:
TServiceCommander.SendCommand(cServiceCommandStopLocationUpdates);
I should update the demo to make this more obvious; thanks!
Hi Dave,
Thanks for the prompt reply.
I had tried to call TServiceCommander.SendCommand(cServiceCommandStopLocationUpdates) both when logout and when app destroy but I still saw the notification even my app was already closed.
By the way I’m using Android 10 and Rio 10.3.3
Regards,
Gerry
Hi Gerry
I have the same problem, did you find a solution to stop the program without it restarting.
Regards
Nikolaj
Hi Dave,
Thanks for the code – all works well, except for my use case which is logging to a website with basic authorization.. I have tried code identical to that which works in a android application as follows:- when that failed I tried to modify your PostRequest.PostRequest (also below). Both work if the directory does not require basic auth and fail if it does.
So having floundered around for a day, i throw myself on your mercy (and made a small donation)
Any help would be very much appreciated
Thanks and Best regards
Alastair Breingan
I also tried modifying your PostRequest.PostRequest as follows:-
Hi Alastair,
What does your Encode64 method do? It seems there’s some parts missing there? I have code elsewhere that provides Basic authentication like this:
Where TNetEncoding comes from System.NetEncoding.
PS: Thanks for the donation!
Hi Dave. I created a new service and configured a TBackgroundMonitor, but when the alarm is triggered I get an intent = nil in StartCommand so FBackgroundMonitor.StartDozeAlarm is not executed. Where am I going wrong?
I’ve replied to the issue you lodged.
Hi again Dave.
Firstly thanks for the demo – i have it working well.
However there is one issue with my use case which is probably nothing to do with your code, but is just confusing me.
I am tracking a pair of volunteer fire trucks so each knows where the other is, and want to start logging when they leave the station (which is a tin shed and so probably bad for GPS signals).
Both tablets (new Samsung) are almost always reporting the same exact lat /long which is over 15km away from the shed, so obviously a defaulted value under some condition (-31.8912036 152.5313308). If i take the units out of the shed they report a roughly correct location which then changes via small increments, and when i put them away again they go back to that same exact location.
So as far as i can see from DW.Location.Android it is GetLastKnownLocation which would first see a failure to obtain a location but the service modules OnLocationChange would appear not to be triggered unless a decent location is obtained.
So i suspect something upstream of your code is doing something weird.
I have checked that Location services are on and on high accuracy.
Is there anything i am missing – it seems very twisted to hard-wire a check for that exact location!
Thanks in advance and best regards
Alastair Breingan
> Both tablets (new Samsung) are almost always reporting the same exact lat /long which is over 15km away from the shed
Do you mean this is happening before they leave, or while they’re outside, moving? It would seem odd if it were the latter.
If it’s a question of GetLastKnownLocation being inaccurate, you could comment out the code in the TLocation.InternalRequestLastKnownLocation method, or just process only location updates that have a source of TLocationSource.Listeners
This only happens when they are in the shed and not moving, so i am guessing that a location is not able to be determined,
It would be nice to continue getting a “heartbeat” but i will start checking the source more carefully and see if i get some further clue.
Thanks again
Alastair Breingan
In case it is useful to others i found the following suggesting some phones inc samsung have problems with GetLastKnownLocation. I am just not using InternalRequestLastKnownLocation which works for me
https://stackoverflow.com/questions/17250968/getlastknownlocation-always-return-the-same-location
HI dave, I’m actually tried your job with delphi 10.4 and SM-A202F, it works wonderfully.
I’m tring to send it direct to MYSQL with mydac lib, but the problem is that when i execut de query under your F.Updater.url line it doesen’t work again.
do you recomend some to get it?
Hard to say what the issue is without seeing your code. I will say however that it’s strongly not recommended to update a remote database direct from a mobile app. It’s recommended to create a REST server that updates the database and have the mobile client make calls to that REST server.
Hello Dave
I have problem when I try to Terminate/Stop the App.
The App will not stop. It keep on start the libCrossPlatformLocationService!
I have try to remove the RestartService in:
procedure TServiceModule.AndroidServiceDestroy(Sender: TObject);
begin
DestroyObjects;
// RestartService; //
end;
Then I can stop the application with application.terminate.
But I can’t stop the app by swiping! It.
Hi Nikol,
You may be able to achieve it by modifying the AndroidManifest.template.xml file, replacing
<%services%>
with
<service android:exported=”false” android:name=”com.embarcadero.services.CrossPlatformLocationService” android:stopWithTask=”true”/>
Unfortunately I’m unable to test this on my device
Hello. ” Undeclared identifier TLocationData” error occurs when I try to build CrossPatformLocation for Android in Delphi 11.
Can you please explain what I’m doing wrong. Thanks.
Hi. “TLocationData undeclared” error occurs when I try to build project CrossPlatformLocationD11 for Android in Delphi 11. Please help.
… for some case “DW.Location.Types” was missing in Uses
Why the Android service is stopped when I close the application? I’m using Delphi 11 and your latest sources for CrossPlatformLocationD11.
The service is stopped if I close the app. Android version: 8.1. How to keep the service running? I’ve also tried on Android 9 phone, the same problem, with one difference: at least the service is automatically started at boot.
I made a change recently that may solve this issue. If not, please file a report here: https://github.com/DelphiWorlds/Kastri/issues
android.permission.ACCESS_BACKGROUND_LOCATION
Can anyone use this permission in Delphi 12.1?
Yes, there’s an option for it in Uses Permissions in the Dangerous section. It’s a permission that needs to be also requested at runtime, which the demo does