Erwin Sienknecht
Erwin Sienknecht

Reputation: 77

Why is the TTakePhotoFromCameraAction.OnDidFinishTaking event not working on Android 10?

Using Delphi 10.4.2 FireMonkey, targeting Android 10 x64.

I'm trying to take a photo and save it for later processing.

First I've tried to use the built-in TTakePhotoFromCameraAction, but the OnDidFinishTaking event doesn't work (the camera screen is visible, I can take the photo and press "ok"), my program shows up, but my TakePhotoFinishTaking() method is not called.

procedure TMainForm.TakePhotoFinishTaking(Image: TBitmap);
begin
  DebugEnter(0, self, 'TakePhotoFinishTaking', [Image]);
  HidePopup;
  SetBilderActions(false);
  try
    PicGrid.RowCount := PicGrid.RowCount+1;
    InsertImageItem('', Image, PicGrid.RowCount-1);
  finally
    SetBilderActions(true);
  end;
  DebugLeave(0, self, 'TakePhotoFinishTaking');
end;

Next I tried to use IFMXCameraService directly:

procedure TMainForm.btKameraClick(Sender: TObject);
var
  Service: IFMXCameraService;
  Params : TParamsPhotoQuery;
begin
  DebugEnter(0, self, 'btKameraClick', [Sender]);
  if PermissionsService.IsEveryPermissionGranted([cPermissionCamera, cPermissionReadExternalStorage, cPermissionWriteExternalStorage]) then begin
    if TPlatformServices.Current.SupportsPlatformService(IFMXCameraService, Service) then begin
      Params.RequiredResolution := TSize.Create(640, 640);
      Params.Editable           := false;
      Params.NeedSaveToAlbum    := true;
      Params.OnDidFinishTaking  := TakePhotoFinishTaking;
      Params.OnDidCancelTaking  := NIL;
      Service.TakePhoto(NIL, Params);
    end else begin
      FMX.DialogService.ASync.TDialogServiceAsync.MessageDialog('Kamerafunktion wird auf diesem Gerät nicht unterstützt!', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0)
    end;
  end else begin
    FMX.DialogService.ASync.TDialogServiceAsync.MessageDialog('Keine Kamera oder externer Speicher Rechte!', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0)
  end;
  DebugLeave(0, self, 'btKameraClick');
end;

Same result: I see the camera screen, take a photo, press Ok, my program comes back, but no OnDidFinishTaking event.

Then I've tried to intercept the messages from the Photo app:

FFinishTakingCamPhotoId   := TMessageManager.DefaultManager.SubscribeToMessage(TMessageDidFinishTakingImageFromCamera,  HandleMessage);
FFinishTakingLibPhotoId   := TMessageManager.DefaultManager.SubscribeToMessage(TMessageDidFinishTakingImageFromLibrary, HandleMessage);
FMessageReceivedImagePath := TMessageManager.DefaultManager.SubscribeToMessage(TMessageReceivedImagePath,               HandleMessage);

All of the subcribeToMessage() calls are returning an ID, but HandleMessage() is NEVER being called for these Types.

This Message (for example) is working very well!

FOrientationChangedId     := TMessageManager.DefaultManager.SubscribeToMessage(TOrientationChangedMessage,              HandleMessage);
procedure TMainForm.HandleMessage(const Sender: TObject; const Msg: TMessage);
var
  ImgPath : String;
begin
  DebugEnter(0, self, 'HandleMessage', [Sender, Msg]);

  {$IFDEF Android}
  if Msg is TMessageResultNotification then begin
    DebugMsg(0, 'TMessageResultNotification');
    OnActivityResult(TMessageResultNotification(Msg).RequestCode, TMessageResultNotification(Msg).ResultCode, TMessageResultNotification(Msg).Value);
  end;
  if Msg is TMessageDidFinishTakingImageFromCamera then begin
    DebugMsg(0, 'TMessageDidFinishTakingImageFromCamera');
    TakePhotoFinishTaking(TMessageDidFinishTakingImageFromCamera(Msg).Value);
  end;
  if Msg is TMessageReceivedImagePath then begin
    DebugMsg(0, 'TMessageReceivedImagePath');
    ImgPath := TMessageReceivedImagePath(Msg).Value;
  end;
  if Msg is TMessageDidFinishTakingImageFromLibrary then begin
    DebugMsg(0, 'TMessageDidFinishTakingImageFromLibrary');
    TakePhotoFinishTaking(TMessageDidFinishTakingImageFromLibrary(Msg).Value);
  end;
  {$ENDIF}

What am I doing wrong?

Upvotes: 2

Views: 1353

Answers (2)

Erwin Sienknecht
Erwin Sienknecht

Reputation: 77

Ok - Problem solved. It was an old Projekt and that was missing the option android:requestLegacyExternalStorage="true" in the application node of Androidmanifest-template.xml.

After fixing this all works fine, even the TakePhotoFromCameraAction with ObDidFinishTaking Event.

Unfortunately there is no Error or Exception where executed...

Thanks @Dave for pointing me in the right direction!

Upvotes: 1

Dave Nottage
Dave Nottage

Reputation: 3612

The reason why your app is not receiving the TMessageDidFinishTakingImageFromCamera message is that the message is not sent if OnDidFinishTaking is assigned to the Params, as per the code in FMX.MediaLibrary.Android:

procedure TImageManagerAndroid.DidReceiveBitmap(const Sender: TObject; const M: TMessage);
var
  ImagePath: string;
  Photo: TBitmap;
  RequestCode: Integer;
begin
  if M is TMessageReceivedImagePath then
  begin
    ImagePath := TMessageReceivedImagePath(M).Value;
    RequestCode := TMessageReceivedImagePath(M).RequestCode;
    Photo := TBitmap.CreateFromFile(ImagePath);
    try
      if Assigned(FParams.OnDidFinishTaking) then
        FParams.OnDidFinishTaking(Photo)
      else
      begin
        if RequestCode = TJFMXMediaLibrary.JavaClass.ACTION_TAKE_IMAGE_FROM_CAMERA then
          TMessageManager.DefaultManager.SendMessage(Self, TMessageDidFinishTakingImageFromCamera.Create(Photo));
        if RequestCode = TJFMXMediaLibrary.JavaClass.ACTION_TAKE_IMAGE_FROM_LIBRARY then
          TMessageManager.DefaultManager.SendMessage(Self, TMessageDidFinishTakingImageFromLibrary.Create(Photo));
      end;
    finally
      Photo.Free;
    end;
  end;
end;

The following code, which is essentially the equivalent as yours, works fine for me using Delphi 10.4.2:

unit Unit1;

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, FMX.Layouts, FMX.Objects;

type
  TForm1 = class(TForm)
    Layout1: TLayout;
    Button1: TButton;
    Image1: TImage;
    procedure Button1Click(Sender: TObject);
  private
    FCameraPermissions: TArray<string>;
    procedure CameraDidFinishTakingHandler(Image: TBitmap);
    procedure TakePhoto;
  public
    constructor Create(AOwner: TComponent); override;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  System.Permissions,
  FMX.Platform.Android,
  FMX.MediaLibrary, FMX.Platform;

const
  cPermissionCamera = 'android.permission.CAMERA';
  cPermissionReadExternalStorage = 'android.permission.READ_EXTERNAL_STORAGE';
  cPermissionWriteExternalStorage = 'android.permission.WRITE_EXTERNAL_STORAGE';

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  FCameraPermissions := [cPermissionReadExternalStorage, cPermissionWriteExternalStorage, cPermissionCamera];
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  PermissionsService.RequestPermissions(FCameraPermissions,
    procedure(const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>)
    begin
      if PermissionsService.IsEveryPermissionGranted(FCameraPermissions) then
        TakePhoto;
    end
  );
end;

procedure TForm1.TakePhoto;
var
  LCameraService: IFMXCameraService;
  LParams: TParamsPhotoQuery;
begin
  if TPlatformServices.Current.SupportsPlatformService(IFMXCameraService, LCameraService) then
  begin
    LParams.RequiredResolution := TSize.Create(640, 640);
    LParams.Editable := False;
    LParams.NeedSaveToAlbum := True;
    LParams.OnDidFinishTaking := CameraDidFinishTakingHandler;
    LParams.OnDidCancelTaking := nil;
    LCameraService.TakePhoto(nil, LParams);
  end;
end;

procedure TForm1.CameraDidFinishTakingHandler(Image: TBitmap);
begin
  Image1.Bitmap.Assign(Image);
end;

end.

Upvotes: 2

Related Questions