Search code examples
androiddelphidelphi-10.2-tokyo

Implementing Java/Android's callback/listeners in Delphi


I'm trying to create an Android app in Delphi 10.2 Tokyo that makes use of Camera2 API. I used Java2OP to create the interfaces that I need. Some of these interfaces are for callback/listeners. For example CameraDevice.StateCallback which Java2OP has translated to this:

JCameraDevice_StateCallbackClass = interface(JObjectClass)
    ['{3F5A7394-FD15-439C-9BFB-DF8D43F9F930}']
    {class} function _GetERROR_CAMERA_DEVICE: Integer; cdecl;
    {class} function _GetERROR_CAMERA_DISABLED: Integer; cdecl;
    {class} function _GetERROR_CAMERA_IN_USE: Integer; cdecl;
    {class} function _GetERROR_CAMERA_SERVICE: Integer; cdecl;
    {class} function _GetERROR_MAX_CAMERAS_IN_USE: Integer; cdecl;
    {class} function init: JCameraDevice_StateCallback; cdecl;
    {class} property ERROR_CAMERA_DEVICE: Integer read _GetERROR_CAMERA_DEVICE;
    {class} property ERROR_CAMERA_DISABLED: Integer read _GetERROR_CAMERA_DISABLED;
    {class} property ERROR_CAMERA_IN_USE: Integer read _GetERROR_CAMERA_IN_USE;
    {class} property ERROR_CAMERA_SERVICE: Integer read _GetERROR_CAMERA_SERVICE;
    {class} property ERROR_MAX_CAMERAS_IN_USE: Integer read _GetERROR_MAX_CAMERAS_IN_USE;
  end;

  [JavaSignature('android/hardware/camera2/CameraDevice$StateCallback')]
  JCameraDevice_StateCallback = interface(JObject)
    ['{3A3944F5-A71F-4CD6-98C6-04B8D65C3B52}']
    procedure onClosed(camera: JCameraDevice); cdecl;//Deprecated
    procedure onDisconnected(camera: JCameraDevice); cdecl;//Deprecated
    procedure onError(camera: JCameraDevice; error: Integer); cdecl;//Deprecated
    procedure onOpened(camera: JCameraDevice); cdecl;//Deprecated
  end;
  TJCameraDevice_StateCallback = class(TJavaGenericImport<JCameraDevice_StateCallbackClass, JCameraDevice_StateCallback>) end;

It is my understanding that I should combine TJavaLocal with the interface that I want to use as callback/listener (in this case JCameraDevice_StateCallbackClass). Here is what I have done: unit CamDevStateCallback;

interface

uses
  Androidapi.JNIBridge, android.hardware.camera2;

type
  TCamera2Event = procedure(camera: JCameraDevice) of object;
  TCamera2ErrorEvent = procedure(camera: JCameraDevice; error: Integer) of object;

  TCamDevStateCallback = class(TJavaLocal, JCameraDevice_StateCallback)

  protected
    FOnClosed: TCamera2Event;
    FOnDisconnected: TCamera2Event;
    FOnError: TCamera2ErrorEvent;
    FOnOpen: TCamera2Event;

  public
    procedure onClosed(camera: JCameraDevice); cdecl;
    procedure onDisconnected(camera: JCameraDevice); cdecl;
    procedure onError(camera: JCameraDevice; error: Integer); cdecl;
    procedure onOpened(camera: JCameraDevice); cdecl;
    class function CreateNew(aOnOpen, aOnClosed, aOnDisconnected: TCamera2Event; aOnError: TCamera2ErrorEvent): JCameraDevice_StateCallback;
    property OnCameraClosed: TCamera2Event read FOnClosed write FOnClosed;
    property OnCameraDisconnected: TCamera2Event read FOnDisconnected write FOnDisconnected;
    property OnCameraError: TCamera2ErrorEvent read FOnError write FOnError;
    property OnCameraOpen: TCamera2Event read FOnOpen write FOnOpen;
  end;

implementation

{ TCamDevStateCallback }

class function TCamDevStateCallback.CreateNew(aOnOpen, aOnClosed, aOnDisconnected: TCamera2Event;
  aOnError: TCamera2ErrorEvent): JCameraDevice_StateCallback;
var
  tmpObj: TCamDevStateCallback;
begin
  tmpObj := TCamDevStateCallback.Create;
  tmpObj.OnCameraClosed := aOnClosed;
  tmpObj.OnCameraDisconnected := aOnDisconnected;
  tmpObj.OnCameraError := aOnError;
  tmpObj.OnCameraOpen := aOnOpen;
  Result := TJCameraDevice_StateCallback.Wrap((tmpObj as ILocalObject).GetObjectID);
end;

procedure TCamDevStateCallback.onClosed(camera: JCameraDevice);
begin
  if Assigned(FOnClosed) then
    FOnClosed(camera);
end;

procedure TCamDevStateCallback.onDisconnected(camera: JCameraDevice);
begin
  if Assigned(FOnDisconnected) then
    FOnDisconnected(camera);
end;

procedure TCamDevStateCallback.onError(camera: JCameraDevice; error: Integer);
begin
  if Assigned(FOnError) then
    FOnError(camera, error);
end;

procedure TCamDevStateCallback.onOpened(camera: JCameraDevice);
begin
  if Assigned(FOnOpen) then
    FOnOpen(camera);
end;

end.

This code does not compile because it complains the equals, toString differ from previous declaration and getClass, notify, notifyAll and wait are missing.

android.hardware.camera2.pas is created by Java2OP and contains the interface definition of JCameraDevice_StateCallback and more.


Solution

  • The underlying problem here, as @DaveNottage has intimated, is that CameraDevice.StateCallback is not a Java listener interface, but a Java callback class (albeit an abstract class that in some ways is therefore like an interface, but isn't an interface).

    Because it is not a Java interface it is not a candidate for implementing with TJavaLocal despite the fact that in Delphi it is represented by an interface (as all Java interfaces and classes are).

    TJavaLocal is for implementing Java interfaces, typically listener interfaces. It does not provide a means to inherit from a Java class (even an abstract Java class).

    The clue is that the important interface created by Java2OP inherits from JObject indicating that it is part of Java's Object-based class hierarchy. A listener interface when imported inherits from IJavaInstance.

    Delphi doesn't support inheriting from Android Java classes (in contrast to its iOS support where Delphi does support inheriting from Objective-C classes, thanks to Objective-C being a native system unlike Android's managed Java VM system)