Saturday, January 2, 2016

Using LocationSensor on Android Service in Delphi Seattle

Hi all,
this is a very great news!
Me and my CEO (Daniele Tetibit 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 :)

13 comments :

  1. Great as usual Daniele Spinetti a.k.a. "Danielino" J

    ReplyDelete
  2. Hi... 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.

    Looking forward to the demo.

    ReplyDelete
  3. Hello.
    I 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 :)

    ReplyDelete
    Replies
    1. It seems strange.. Are you sure that you have done all fixes as shown ?

      Delete
    2. Yes, 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.
      May be we have different Delphi versions?
      I work in Delphi 10 Seattle (without any updates).

      Sorry for english.

      Best regards, Ruslan

      Delete
    3. Thank you for solving my problem )

      Delete
  4. Olá, 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

    ReplyDelete
  5. Olá.. Preciso de sua ajuda, estou tenando fazer rodar o programa mas acusa a falta dos arquivos System.Android.SensorsDD.pas e System.SensorsDD.pas ..
    Poderia disponibilizar o projeto completo com estas units?

    Desde já agradeço.

    ReplyDelete
    Replies
    1. 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.
      Ps. For future comments please use english language, thanks.

      Delete