Search code examples
androiddelphiforeground-service

Delphi android - Send data from local foreground service to application


I have an application launching a foreground local service. I'm able to send commands from application to the service. I also need to send data (Ex : location) from the foreground local service to the application. Embarcadero provides an example using notification center but it does not work at all : http://docwiki.embarcadero.com/CodeExamples/Sydney/en/FMX.Android_Notification_Service_Sample. I compiled the sample and installed it on my device, nothing is received, of course same behaviour with my application : I get the notification in device, not in the application.

I read a lot of posts about communication between service and application, most of them in java, but I have no clue of how to implement it with delphi. I guess the simplest way is to use intent as I have no much data to send. Something like this ? :

In service something like this :

IntentMes := TJIntent.Create;
IntentMes.addCategory(TJIntent.JavaClass.CATEGORY_DEFAULT);
IntentMes.setAction(StringToJString(svcIntentAction)); // svcIntentAction = fr.MyApp.SVC
IntentMes.putExtra(StringToJString(svcIntentData),StringToJString('Hello'));
TAndroidHelper.Context.sendBroadcast(Intent); // Very not sure of that

In application something like that :

function MyApp.HandleIntentAction(const Data: JIntent): Boolean;
var svcData : string;
begin
  Result := False;
  if Data <> nil then begin
    if Data.hasExtra(StringToJString(svcIntentData)) then begin
      svcData := JStringToString(Data.getStringExtra(StringToJString(svcIntentData)));
      result := true;

      // process data...
    end;

  end;
end;

In manifest file :

<intent-filter>  
    <action android:name="fr.MyApp.SVC" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter> 

I also need to be sure that application is running before sending data.


Solution

  • This is one possible way of doing what you want, based on some code based on what I use in this demo:

    https://github.com/DelphiWorlds/Kastri/tree/master/Demos/CrossPlatformLocation

    What follows is the basics of that work, but the premise is the same, i.e. create an instance of a class that implements JFMXBroadcastReceiverListener, which adds the appropriate action, and assign a handler to the OnMessageReceived event. "Data" are sent via the SendMessage method.

    unit Unit1;
    
    interface
    
    uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
      FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
      Androidapi.JNIBridge, Androidapi.JNI.JavaTypes, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Embarcadero;
    
    type
      // Import that is missing from Delphi - it would pay to split this off into a separate unit
      JLocalBroadcastManager = interface;
    
      JLocalBroadcastManagerClass = interface(JObjectClass)
        ['{5CCF81B2-E170-47C4-873E-32E644085880}']
        {class} function getInstance(context: JContext): JLocalBroadcastManager; cdecl;
      end;
    
      {$IF CompilerVersion < 35}
      [JavaSignature('android/support/v4/content/LocalBroadcastManager')]
      {$ELSE}
      [JavaSignature('androidx/localbroadcastmanager/content/LocalBroadcastManager')]
      {$ENDIF}
      JLocalBroadcastManager = interface(JObject)
        ['{B5D9B2DA-E150-4CC5-BBDA-58FCD42C6C1E}']
        procedure registerReceiver(receiver: JBroadcastReceiver; filter: JIntentFilter); cdecl;
        function sendBroadcast(intent: JIntent): Boolean; cdecl;
        procedure sendBroadcastSync(intent: JIntent); cdecl;
        procedure unregisterReceiver(receiver: JBroadcastReceiver); cdecl;
      end;
      TJLocalBroadcastManager = class(TJavaGenericImport<JLocalBroadcastManagerClass, JLocalBroadcastManager>) end;
    
      TMessageReceivedEvent = procedure(Sender: TObject; const Msg: string) of object;
    
      // Local broadcast receiver class
      TLocalReceiver = class(TJavaLocal, JFMXBroadcastReceiverListener)
      private
        FAction: JString;
        FBroadcastReceiver: JFMXBroadcastReceiver;
        FIntentFilter: JIntentFilter;
        FOnMessageReceived: TMessageReceivedEvent;
        procedure DoMessageReceived(const AMsg: string);
      public
        { JFMXBroadcastReceiverListener }
        procedure onReceive(context: JContext; intent: JIntent); cdecl;
      public
        constructor Create(const AAction: string);
        destructor Destroy; override;
        property OnMessageReceived: TMessageReceivedEvent read FOnMessageReceived write FOnMessageReceived;
      end;
    
      TForm1 = class(TForm)
      private
        FReceiver: TLocalReceiver;
        procedure ReceiverMessageReceivedHandler(Sender: TObject; const AMsg: string);
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.fmx}
    
    uses
      Androidapi.Helpers;
    
    const
      cLocalBroadcastExtraMessage = 'MESSAGE';
    
    // e.g. SendMessage('SERVICE_MESSAGE', 'Hello world');
    procedure SendMessage(const AAction, AMessage: string);
    var
      LIntent: JIntent;
    begin
      LIntent := TJIntent.JavaClass.init(StringToJString(AAction));
      LIntent.putExtra(StringToJString(cLocalBroadcastExtraMessage), StringToJString(AMessage));
      TJLocalBroadcastManager.JavaClass.getInstance(TAndroidHelper.Context).sendBroadcast(LIntent);
    end;
    
    { TLocalReceiver }
    
    constructor TLocalReceiver.Create(const AAction: string);
    begin
      inherited Create;
      FAction := StringToJString(AAction);
      FBroadcastReceiver := TJFMXBroadcastReceiver.JavaClass.init(Self);
      FIntentFilter := TJIntentFilter.JavaClass.init;
      FIntentFilter.addAction(FAction);
      TJLocalBroadcastManager.JavaClass.getInstance(TAndroidHelper.Context).registerReceiver(FBroadcastReceiver, FIntentFilter);
    end;
    
    destructor TLocalReceiver.Destroy;
    begin
      TJLocalBroadcastManager.JavaClass.getInstance(TAndroidHelper.Context).unregisterReceiver(FBroadcastReceiver);
      FBroadcastReceiver := nil;
    end;
    
    procedure TLocalReceiver.onReceive(context: JContext; intent: JIntent);
    begin
      if intent.getAction.equals(FAction) then
        DoMessageReceived(JStringToString(intent.getStringExtra(StringToJString(cLocalBroadcastExtraMessage))));
    end;
    
    procedure TLocalReceiver.DoMessageReceived(const AMsg: string);
    begin
      if Assigned(FOnMessageReceived) then
        FOnMessageReceived(Self, AMsg);
    end;
    
    { TForm1 }
    
    constructor TForm1.Create(AOwner: TComponent);
    begin
      inherited;
      // For the service, do a similar setup with an instance of TLocalReceiver, but use an action of something like 'APP_MESSAGE'
      FReceiver := TLocalReceiver.Create('SERVICE_MESSAGE'); // i.e. receiving messages from the service
      FReceiver.OnMessageReceived := ReceiverMessageReceivedHandler;
    end;
    
    destructor TForm1.Destroy;
    begin
      FReceiver.Free;
      inherited;
    end;
    
    procedure TForm1.ReceiverMessageReceivedHandler(Sender: TObject; const AMsg: string);
    begin
      // Handle AMsg here
    end;
    
    end.