Search code examples
androiddelphifiremonkey

Delphi AndroidAPi motion detection


I want to code a motion detector (Accelerometer) using the AndroidApi directly. I currently have tested implementations (per Embarcadero examples) using TMotionSensor as well as TSensorManager, but both seem to have power drain issues (ie. the phone gets hot).

My TSensorManager implementation looks like this:

procedure TfrmTabbed.InitSensorMan;
var
  FSensors: TSensorArray;
  Sensor: TCustomSensor;
begin
  TSensorManager.Current.Activate;
  FSensors := TSensorManager.Current.GetSensorsByCategory(TSensorCategory.Motion);
  FSensor := nil;
  for Sensor in FSensors do
  begin
    if TCustomMotionSensor(Sensor).SensorType = TMotionSensorType.Accelerometer3D then
    begin
      FSensor := TCustomMotionSensor(Sensor);
      Break;
    end;
  end;
  MotionTimer.Interval := 250; 
  MotionTimer.Active := True;
end;

So now, using How to detect movement of an android device? as a reference, I start writing code like this:

uses
  Androidapi.Sensor,
  Androidapi.JNI.JavaTypes;

{$R *.fmx}

procedure TForm2.FormCreate(Sender: TObject);
var
  Obj: JObject;
  SensorManager: JSensorManager;
begin

  Obj := TAndroidHelper.Context.getSystemService(TJContext.JavaClass.SENSOR_SERVICE);
  if Obj <> nil then
  begin
     SensorManager := TJsensorManager.Wrap(Obj);

  end;

I guess anyone who is familiar with this area, will realize that there's no JSensorManager declared anywhere in C:\Program Files (x86)\Embarcadero\Studio\18.0\source. There's a Androidapi.JNI.Telephony.pas, but no Androidapi.JNI.Sensor(s).pas!

My question is, is it possible to access the SENSOR_SERVICE from Delphi in this way, and if so, how can I implement it?

Addendum

I tried Java2op. It seems to require a very specific version (1.7.25?) of the JDK to not produce a "class or interface expected" error. So I tried Java2Pas instead. The free version only parses Android.jar, but that seems to be sufficient for my purposes.


Solution

  • Here is my (almost completed) answer for those of you who are trying to do the same (or similar) things, especially with Listeners, in Delphi Android 10.x.

    Note:

    1. SensorListener has been deprecated in Android. Use SensorEventListener instead.

    2. Java2pas coded the JSensorEventListener in android.hardware.SensorEventListener.pas incorrectly. It was JSensorEventListener = interface(JObject). It must be corrected to JSensorEventListener = interface(IJavaInstance).

    3. In android.hardware.SensorEvent, the values property and the _Getvalues function, was missing from the JSensorEvent declaration. Just copy them from the JSensorEventClass.

    Here is the code:

    unit Unit2;
    
    interface
    
    uses    
     System.SysUtils, System.Types, System.UITypes, System.Classes,
     System.Variants,
     FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
     FMX.Controls.Presentation, FMX.StdCtrls
     , Androidapi.JNIBridge
     , Androidapi.JNI.Embarcadero
     , Androidapi.JNI.GraphicsContentViewText
     , androidapi.JNI.JavaTypes
     , AndroidApi.JNI.Location
     , Androidapi.JNI.Os
     // java2pas gen'd units (modified as noted above)
     , android.hardware.SensorManager
     , android.hardware.Sensor
     , android.hardware.SensorEventListener
     , android.hardware.SensorEvent
     , FMX.ScrollBox, FMX.Memo
     ;
    
    type
    
      TSensorEventListener = class;
    
      TForm2 = class(TForm)
        Button1: TButton;
        Label1: TLabel;
        Memo1: TMemo;
        procedure Button1Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
      private
        FSensorManager: JSensorManager;
        FDefaultSensor: JSensor;
        SensorEventListener: TSensorEventListener;
        FStarted: Boolean;
        procedure StartApi;
        procedure StopApi;
      public
        { Public declarations }
      end;
    
      TSensorEventListener = class(TJavaLocal, JSensorEventListener)
      private
        [weak]
        FParent: TForm2;
      public
        constructor Create(AParent: TForm2);
        procedure onAccuracyChanged(JSensorparam0 : JSensor; Integerparam1 : Integer) ; cdecl;
        procedure onSensorChanged(JSensorEventparam0 : JSensorEvent) ; cdecl;
      end;
    
    var
      Form2: TForm2;
    
    implementation
    
    uses
      Androidapi.Helpers
      , Androidapi.JNI.Net
      , FMX.Helpers.Android
      ;
    
    procedure TForm2.Button1Click(Sender: TObject);
    begin
      if Fstarted then
        StopApi
      else
        StartApi;
    end;
    
    procedure TForm2.FormCreate(Sender: TObject);
    begin
      FStarted := False;
    end;
    
    procedure TForm2.StartApi;
    var
      SensorManagerService: JObject;
    begin
      if not Assigned(FSensorManager) then
      begin
        SensorManagerService := TAndroidHelper.Context.getSystemService(TJContext.JavaClass.SENSOR_SERVICE);
        FSensorManager := TJSensorManager.Wrap((SensorManagerService as ILocalObject).GetObjectID);
        if not Assigned(SensorEventListener) then
          SensorEventListener := TSensorEventListener.Create(Self);
        FDefaultSensor := FSensorManager.getDefaultSensor(TJSensorManager.JavaClass.SENSOR_ACCELEROMETER);
      end;
      FSensorManager.registerListener(SensorEventListener, FDefaultSensor, TJSensorManager.JavaClass.SENSOR_DELAY_NORMAL);
      Memo1.Lines.Add(DateTimeToStr(Now) + ' started');
      FStarted := True;
    end;
    
    procedure TForm2.StopApi;
    begin
      if Assigned(SensorEventListener) then
        FSensorManager.unregisterListener(SensorEventListener);
      Memo1.Lines.Add(DateTimeToStr(Now) + ' stopped');
      FStarted := False;
    end;
    
    { TSensorEventListener }
    
    constructor TSensorEventListener.Create(AParent: TForm2);
    begin
      inherited Create;
      FParent := AParent;
    end;
    
    procedure TSensorEventListener.onAccuracyChanged(JSensorparam0: JSensor;
      Integerparam1: Integer);
    begin
       // do stuff
    end;
    
    procedure TSensorEventListener.onSensorChanged(
      JSensorEventparam0: JSensorEvent);
    begin
       // do stuff, especially with the JSensorEventparam0.values
    end;