this is a very great news!
Me and my CEO (Daniele Teti, bit Time Professionals) have figured out how to implement and use the LocationSensor on Android Service.
Some months ago, I found out that there is a malfunction in Delphi: If you put LocationSensor component on Android Service DM, the service crashes. So I opened an issue on the quality portal, which was recognized and classified with majority prior.
However we need to retrieve the Device position from a background Service not by a foreground Activity.
We have investigated into the code and found this mistake in System.Android.Sensors unit:
- For the majority of the code, it is assumed that the context of Application is an Activity
Here is the code:
FActivity := TJNativeActivity.Wrap(System.DelphiActivity)^.clazz);
The code above works fine if you are in Android Activity context, like our case. If you are in a service context though, this doesn't work (the DelphiActivity pointer is null, rightly). If there is not necessity to have a reference to the Activty, but only the context of Application, this code is to be preferred:
LContext := TJContextWrapper.Wrap(System.JavaContext);
Below you can find the System.Sensors and System.Android.Sensors units (opportunely renamed System.SensorsDD and System.Android.SensorsDD), with the fixes explained earlier (the code contains comments for any changes):
System.SensorsDD:
// about line 748
implementation
uses
System.Variants, System.Math, System.Character,
{$IFDEF ANDROID}
// -- patch dd
// include the modified System.Android.SensorsDD
System.Android.SensorsDD;
{$ENDIF ANDROID}
System.Android.SensorsDD:
...
//about line 12
uses
// -- patch dd
// use the modified System.SensorsDD
System.SensorsDD;
...
// about line 70
class constructor TPermission.Create;
var
PackageInfo: JPackageInfo;
PackageManager: JPackageManager;
Activity: JActivity;
LContext: JContext;
begin
// -- patch dd
// Activity := TJNativeActivity.Wrap
// (PANativeActivity(System.DelphiActivity)^.clazz)
LContext := TJContextWrapper.Wrap(System.JavaContext);
PackageManager := LContext.getPackageManager();
PackageInfo := PackageManager.getPackageInfo
(LContext.getApplicationContext.getPackageName,
TJPackageManager.JavaClass.GET_PERMISSIONS);
FPermissions := PackageInfo.requestedPermissions;
end;
...
// about line 100
type
TAndroidGeocoder = class(TGeocoder)
private type
TGeocoderRunnable = class(TJavaLocal, JRunnable)
private
FCoord: TLocationCoord2D;
FLGeocoder: JGeocoder;
public
constructor Create(ACoord: TLocationCoord2D; AGeocoder: JGeocoder);
procedure run; cdecl;
end;
private
class var
FGeocoder: JGeocoder;
// FActivity: JActivity; // -- patch dd
FActivity: JContextWrapper; // -- patch dd
...
// about line 130
TUIAndroidLocationSensor = class(TCustomLocationSensor)
private
FPermitted: Boolean;
// FActivity: JNativeActivity; // -- patch dd
FActivity: JContext; // -- patch dd
FLastValue: JLocation;
FLocationManager: JLocationManager;
FAccuracy: TLocationAccuracy;
FDistance: TLocationDistance;
...
// about line 1600
constructor TUIAndroidLocationSensor.Create(AManager: TSensorManager);
var
LocationService: JObject;
begin
inherited;
// FActivity := TJNativeActivity.Wrap
// (PANativeActivity(System.DelphiActivity)^.clazz); // -- patch dd
FActivity := TJContext.Wrap(System.JavaContext); // -- patch dd
LocationService := FActivity.getSystemService
(TJContext.JavaClass.LOCATION_SERVICE);
if Assigned(LocationService) then
FLocationManager := TJLocationManager.Wrap((LocationService as ILocalObject)
.GetObjectID);
end;
...
// about line 1630
function RunIfPossible(var ARunnable: TLocationRunnable;
var AListener: TLocationListener; AProviderName: JString): Boolean;
var
Provider: JLocationProvider;
LHandler: JHandler;
begin
Result := False;
if FLocationManager.isProviderEnabled(AProviderName) then
begin
if AListener = nil then
AListener := TLocationListener.Create(Self);
Provider := FLocationManager.getProvider(AProviderName);
if Provider <> nil then
begin
ARunnable := TLocationRunnable.Create(FLocationManager, AListener,
AProviderName);
// FActivity.runOnUiThread(ARunnable); // --patch dd
// -- patch dd
// You can use post method of Handler instead runOnUiThread in this case.
// more info here: http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html
LHandler := TJHandler.JavaClass.init;
LHandler.post(ARunnable);
Result := True;
end;
end;
end;
...
// about line 1830
class constructor TAndroidGeocoder.Create;
begin
// -- patch dd
// FActivity := TJNativeActivity.Wrap
// (PANativeActivity(System.DelphiActivity)^.clazz);
FActivity := TJContextWrapper.Wrap(System.JavaContext);
FGeocoder := TJGeocoder.JavaClass.init(FActivity);
end;
Now you are able to using LocationSensor in this way:
uses
System.SensorsDD, System.Android.SensorsDD
...
...
var
FSensors: TSensorArray;
Sensor: TCustomSensor;
begin
TSensorManager.Current.Active := true;
FSensors := TSensorManager.Current.GetSensorsByCategory(TSensorCategory.Location);
FSensor := nil;
for Sensor in FSensors do
begin
if TCustomLocationSensor(Sensor).SensorType = TLocationSensorType.GPS then
begin
FSensor := TCustomLocationSensor(Sensor);
Break;
end;
end;
if not Assigned(FSensor) then
Exit; { no location sensor is available }
{ start the sensor if it is not started }
if not FSensor.Started then
FSensor.Start;
You can find a complete demo here. Hopefully this solution will be helpful to you the way it was for us.
P.S.
Stay tuned as because of this fix a very interesting application/project is about to see the light of day :)
Demo please
ReplyDeleteUpdated with demo url :)
DeleteGreat as usual Daniele Spinetti a.k.a. "Danielino" J
ReplyDeleteThank you "cavo" J
DeleteHi... I had spoken at length about this issue to Jim McKeeth along with some other minor bugs in Android services. I am glad that this is resolved.
ReplyDeleteLooking forward to the demo.
Updated with demo url :)
DeleteHello.
ReplyDeleteI made your changes on System.Android.SensorsDD.pas and System.SensorsDD.pas and put it in folder "LocationSensorPatch\patch"
Then i compile project, i get errors:
"[DCC Error] System.SensorsDD.pas(805): E2010 Incompatible types: 'TSensorManager.TSensorManagerType' and 'class of TPlatformSensorManager'
[DCC Error] System.SensorsDD.pas(1144): E2361 Cannot access private symbol TGeocoder.GeocoderImplementer
[DCC Error] System.SensorsDD.pas(1449): E2362 Cannot access protected symbol TPlatformGpsStatus.GetGpsStatusImplementer"
Please help :)
It seems strange.. Are you sure that you have done all fixes as shown ?
DeleteYes, i've done all fixes as shown. The different is that in your post fixes i see "// about line 1600", but in my System.Android.Sensors.pas it's near line 1500 and "// about line 1630" is near line 1530.
DeleteMay be we have different Delphi versions?
I work in Delphi 10 Seattle (without any updates).
Sorry for english.
Best regards, Ruslan
Thank you for solving my problem )
Deletehi, i have same problem... how to solve it?
DeleteHello, how are you
DeleteI'm redoing your example, but this is giving me a run-time error java.lang.runtime.exception: Can not create handler inside thread that has not called Looper.prepare () ...... Can you help me ??
This exception occurs when you try to do somenthing in a secondary Thread, while should be done in Main/Graphic Thread
DeleteOlá, estou aplicando sua rotina de programação, más estou com um problema, o programa funciona corretamente, más as coordenadas Latitude e longitude estão em branco, pode me ajudar, muito grato
ReplyDeleteOlá.. Preciso de sua ajuda, estou tenando fazer rodar o programa mas acusa a falta dos arquivos System.Android.SensorsDD.pas e System.SensorsDD.pas ..
ReplyDeletePoderia disponibilizar o projeto completo com estas units?
Desde já agradeço.
Hello, the code of System.Android.Sensors.pas and System.Sensors.pas are properties of Embarcadero that have the copyrights, so I cannot redistribute the entire code/unit but only show my changes.
DeletePs. For future comments please use english language, thanks.
Hi!
ReplyDeleteDo you have an idea how to use a similar solution for receiving CallStateChanges from the Telephony? Similar to the OnCallStateChanged solution? Is there a similar way like this, to use the IFMXPhoneDialerService interface on an AndroidService?
Thanks,
Laszlo
Hi, you can use the TPlatformServices with IFMXPhoneDialerService to detect the call state changes as described here http://docwiki.embarcadero.com/RADStudio/Berlin/en/Mobile_Tutorial:_Using_the_Phone_Dialer_on_Mobile_Devices_(iOS_and_Android). It should work also on Android Service. Have you tried ?
DeleteHi!
DeleteYes, well actually putting any FMX. unit into the service freezes it.
So for example: uses FMX.Platform, FMX.PhoneDialer;
FMX stuff doesn't seem to work on Android Service, as far as I know.
Ok! Now I understand... I'll do a check ASAP
DeleteDid you have a chance to test it?
Deletecan i use this demo in delphi berlin 10.1 ??
ReplyDeleteYes, it should work!
DeleteHi Daniele, i dowload all the code from here https://github.com/spinettaro/DelphiDemos. I open the project DemosPG.groupproj and i run (F9) the i get this error: [DCC Error] MainFMX.pas(69): E2035 Not enough actual parameter. I too try to open LocationSensorPatch.groupproj and when i run this project i get this error: [DCC Fatal Error] LocationServiceU.pas(10): F2613 Unit '..\patch\System.Android.SensorsDD.pas' not found. How i can to test this example?
ReplyDeleteI am in them. What happens is that the service refers to those .pas and the same are not! =)
DeleteFor me, the demo runs great, but after 30-40 minutes, the service stops. How far did you test it? Did it work for at least a full day for you?
ReplyDeleteI tested it for several hours, not a full day, but for that time it works fine for me
DeleteThe demo work fine on android 5.0, but it seems not get OnLocationChanged call back on android 6.0. Any suggestion? Thanks!
ReplyDeleteHello sir. How to turn off the Location Sensor just to save the Battery? I have used the Android Timer https://forums.embarcadero.com/message.jspa?messageID=915791 to have it run every given time. Somehow, there is no code that can switch off the Location Sensor. I tried "TSensorManager.Current.Active := False". "FSensor.Stop" but to no avail.
ReplyDelete