How to invoke native camera in Delphi Android application? - android

I'm using Delphi 10.3 Community Edition to write a simple Android app and trying to invoke native device camera, but getting an error instead.
I'm following the official Delphi guide:
On the Form Designer, select the button (for taking a photo).
In the Object Inspector, select the drop-down list for the Action property.
Select New Standard Action | Media Library | TTakePhotoFromCameraAction:
On the Events tab, expand the Action node, and then double-click the OnDidFinishTaking event.
Add the following code to the OnDidFinishTaking event handler:
procedure TForm1.TakePhotoFromCameraAction1DidFinishTaking(Image: TBitmap);
begin
Image1.Bitmap.Assign(Image);
end;
This code assigns a picture taken from the mobile device camera to the Bitmap property of the TImage component.
I have verified that Project | Options | Uses Permissions - Camera setting is set to true. I'm requesting required permission on the app start as well. There's no difference between running in Debug or Release.
However there is a problem. When clicking the button I get the following error message:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference.
Here's the code I've written for the simplest test app:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, System.Permissions,
FMX.StdCtrls, FMX.MediaLibrary, FMX.Platform, System.Messaging, FMX.Objects,
System.Actions, FMX.ActnList, FMX.StdActns, FMX.MediaLibrary.Actions,
FMX.Controls.Presentation;
type
TForm1 = class(TForm)
Button1: TButton;
ActionList1: TActionList;
TakePhotoFromCameraAction1: TTakePhotoFromCameraAction;
procedure FormCreate(Sender: TObject);
private
procedure PermissionRequestResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>);
procedure DisplayRationale(Sender: TObject; const APermissions: TArray<string>; const APostRationaleProc: TProc);
end;
var
Form1: TForm1;
implementation
uses
{$IFDEF ANDROID}
Androidapi.Helpers,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.Os,
{$ENDIF}
FMX.DialogService;
{$R *.fmx}
procedure TForm1.PermissionRequestResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>);
begin
// 3 permission involved
if (Length(AGrantResults) = 3)
and (AGrantResults[0] = TPermissionStatus.Granted)
and (AGrantResults[1] = TPermissionStatus.Granted)
and (AGrantResults[2] = TPermissionStatus.Granted) then
else
ShowMessage('Required permission has not been granted') ;
end;
procedure TForm1.DisplayRationale(Sender: TObject; const APermissions: TArray<string>; const APostRationaleProc: TProc);
begin
TDialogService.ShowMessage('Need to access the camera',
procedure(const AResult: TModalResult)
begin
APostRationaleProc;
end);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
permCam, permRead, permWrite: string;
begin
// Request permissions
permCam := JStringToString(TJManifest_permission.JavaClass.CAMERA);
permRead := JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE);
permWrite := JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE);
PermissionsService.RequestPermissions([permCam, permRead, permWrite], PermissionRequestResult, DisplayRationale);
end;
end.
How to make the native camera TTakePhotoFromCameraAction to work?

Check that the Project Options > Entitlements List > Secure File Sharing option is set to true.

For anyone porting a project from an older version to 10.3, make sure that your AndroidManifest.xml includes the <%provider%> tag right above the <%application-meta-data%> tag.
A lot of comments I've found on other sides and here are suggesting that this file can be found here:
C:\Users\(yourusername)\AppData\Roaming\Embarcadero\BDS\20.0\AndroidManifest.xml
But if that doesn't work then you likely already have an AndroidManifest.template.xml file in your source directory. If that is the case then the compiler will use this template file and ignore the file in the AppData folder!

I was having issues where Android 9 would work but some devices running Android 10 wouldn't. I needed to do the steps in the answers listed above but mine was still not working until I added:
android:requestLegacyExternalStorage="true"
to the application section of my AndroidManifest.template.xml

Related

How to get phone (android) call state using Delphi's PhoneDialerService?

I need to get information about outgoing and incoming calls on an Android phone using Delphi 10.3 Rio.
There's many info I want to get about the calls (it's duration, the number of the other phone, if the call was made from this phone or received, etc.), but for now I'm starting with the (I think) most basic information I can get and check for the call state (dialing, connected, disconnected and so on).
I know there's a function for that in Delphi but I haven't been able to make it work.
I've been following Delphi's tutorial on how to use the IFMXPhoneDialerService to do so, but it doesn't seem to work.
I reassigned IFMXPhoneDialerService's OnCallStateChanged event to a custom one but the custom one is never reached.
Here's how I've been doing it
interface
uses
FMX.Controls,
FMX.Controls.Presentation,
FMX.Dialogs,
FMX.Edit,
FMX.Forms,
FMX.PhoneDialer,
FMX.Platform,
FMX.StdCtrls,
FMX.Types,
System.Classes;
type
TQReportsDialerForm = class(TForm)
private
PhoneDialerService: IFMXPhoneDialerService;
procedure MyOnCallStateChanged(const ACallID: String;
const ACallState: TCallState);
public
constructor Create(AOwner: TComponent); override;
end;
var
QReportsDialerForm: TQReportsDialerForm;
implementation
{$R *.fmx}
{$R *.XLgXhdpiTb.fmx ANDROID}
{$R *.LgXhdpiPh.fmx ANDROID}
procedure TQReportsDialerForm.MyOnCallStateChanged(const ACallID: string;
const ACallState: TCallState);
var
outText: string;
begin
Log.d('Entered custom call state handler');
case ACallState of
TCallState.Connected:
outText := 'Connected';
TCallState.Dialing:
outText := 'Dialing';
TCallState.Disconnected:
outText := 'Disconnected';
TCallState.Incoming:
outText := 'Incoming';
TCallState.None:
outText := 'No calls';
end;
lblCallState.Text := outText;
end;
constructor TQReportsDialerForm.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
TPlatformServices.Current.SupportsPlatformService(IFMXPhoneDialerService,
IInterface(PhoneDialerService));
if Assigned(PhoneDialerService) then
begin
PhoneDialerService.OnCallStateChanged := MyOnCallStateChanged;
Log.d('PhoneDialerService correctly assigned.');
end
else
Log.d('Couldn''t assign PhoneDialerService.');
end;
end.
I know the dialer service is assigned because the code logs the PhoneDialerService correctly assigned. message, but when a call state changes it doesn't log the Entered custom call state handler message on MyOnCallStateChanged procedure.
As an additional info, something I found rather strange is that when I'm debugging the app, if I look at the type of OnCallStateChanged after assigning it, it shows as an "Erroneous type", so I think I may be doing something wrong there, but I haven't been able to see where the error is.

How to redirect the app to location sensor settings in android using delphi

I'm using Delphi 10.1 Berlin for developing android mobile application. And in that I need to check if the location sensor is not switched ON, then I need to redirect to the Location sensor Settings in android mobile. How can I able to implement using Delphi? I have seen example using JAVA but not found for Delphi. And Thanks in Advance.
You could try some code like the following. Here is a helper unit:
unit LocationU;
interface
function IsGPSProviderEnabled: Boolean;
function IsNetworkProviderEnabled: Boolean;
procedure LaunchLocationSettings;
implementation
uses
System.SysUtils,
Androidapi.Helpers,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Location,
Androidapi.JNI.Provider;
function IsProviderEnabled(const Provider: JString): Boolean;
var
LocationManagerObj: JObject;
LocationManager: JLocationManager;
begin
LocationManagerObj := TAndroidHelper.Context.getSystemService(
TJContext.JavaClass.LOCATION_SERVICE);
LocationManager := TJLocationManager.Wrap(LocationManagerObj);
Result := LocationManager.isProviderEnabled(Provider);
end;
function IsGPSProviderEnabled: Boolean;
begin
Result := IsProviderEnabled(TJLocationManager.JavaClass.GPS_PROVIDER);
end;
function IsNetworkProviderEnabled: Boolean;
begin
Result := IsProviderEnabled(TJLocationManager.JavaClass.NETWORK_PROVIDER);
end;
procedure LaunchLocationSettings;
begin
TAndroidHelper.Activity.startActivity(
TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_LOCATION_SOURCE_SETTINGS));
end;
end.
and here is some code that calls it:
uses
FMX.Helpers.Android,
Androidapi.Helpers,
Androidapi.JNI.Widget,
LocationU;
procedure TMainForm.FormCreate(Sender: TObject);
begin
if not IsGPSProviderEnabled and not IsNetworkProviderEnabled then
begin
CallInUiThread(
procedure
begin
TJToast.JavaClass.makeText(
TAndroidHelper.Context,
StrToJCharSequence('Location services not enabled - launching settings'),
TJToast.JavaClass.LENGTH_SHORT).show
end);
LaunchLocationSettings;
end;
end;

Firemonkey blocking dialogs on Android

I am porting an older project (app for iOS ans Android) for a customer from an older AppMethod version to the newest RAD Studio version (10.0 Berlin). MessageDlg and similar were used often in the project but this does not work anymore. The apps shows a message, that modal dialogs are not supported on Android. I know and understand why this is so for the Android platform (so please don't mark this question as a dublicate by referencing to an explanation - I'm asking for something else!).
Firemonkey allows to set an anonymous function to be executed asynchronously after the user taps on a button in the dialog box or when it is closed. However, there is often code which depends on users decision or code which must be executed after the dialog box, regardless of which button the user taps on. For example, a dialog box must ask the user for his decision in the middle of an ongoing operation. Further operations then depend on the users decision (which might also include to stop further code execution in the current application!). Since RAD Studio does not support blocking dialogs on Android, I seem to be forced to break up my code - from one function to multiple functions (worse readability...). This gets even more complicated when there are nested function calls with possible modal dialog boxes, which require user interaction.
Is there still a way to emulate blocking dialogs somehow? It doesn't has to be perfect. I just don't want to rewrite a lot of code and outsource even small code fragments into many separate functions, everwhere where user interaction is required.
OK, I think the following "dirty" solution might be sufficient for me:
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.DialogService,
FMX.Controls.Presentation, FMX.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
blockDlg: array[0..99] of Boolean;
blockDlgIdx: Integer = 0;
implementation
{$R *.fmx}
procedure NShowMessageAsync(AMessage: String);
begin
TDialogService.ShowMessage(AMessage);
end;
procedure NShowMessageSync(AMessage: String);
var
locBlockIdx: Integer;
begin
if blockDlgIdx = Length(blockDlg)-1 then blockDlgIdx := 0 else Inc(blockDlgIdx);
locBlockIdx := blockDlgIdx;
blockDlg[locBlockIdx] := true;
TDialogService.ShowMessage(AMessage,
procedure(const AResult: TModalResult)
begin
blockDlg[locBlockIdx] := false;
end);
while blockDlg[locBlockIdx] do begin
Sleep(40);
Application.ProcessMessages();
end;
end;
procedure NMessageDialogAsync(const AMessage: string; const ADialogType: TMsgDlgType;
const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn;
const AHelpCtx: LongInt);
begin
TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton,
AHelpCtx, procedure(const AResult: TModalResult) begin end);
end;
function NMessageDialogSync(const AMessage: string; const ADialogType: TMsgDlgType;
const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn;
const AHelpCtx: LongInt): TModalResult;
var
locBlockIdx: Integer;
LModalResult: TModalResult;
begin
Result := -1;
if blockDlgIdx = Length(blockDlg)-1 then blockDlgIdx := 0 else Inc(blockDlgIdx);
locBlockIdx := blockDlgIdx;
blockDlg[locBlockIdx] := true;
TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton, AHelpCtx,
procedure(const AResult: TModalResult)
begin
LModalResult := AResult;
blockDlg[locBlockIdx] := false;
end);
while blockDlg[locBlockIdx] do begin
Sleep(40);
Application.ProcessMessages();
end;
Result := LModalResult;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
mr: TModalResult;
begin
mr := NMessageDialogSync('1',
System.UITypes.TMsgDlgType.mtInformation,
[System.UITypes.TMsgDlgBtn.mbYes, System.UITypes.TMsgDlgBtn.mbNo, System.UITypes.TMsgDlgBtn.mbCancel],
System.UITypes.TMsgDlgBtn.mbYes,
0);
NShowMessageSync(IntToStr(mr));
mr := NMessageDialogSync('2',
System.UITypes.TMsgDlgType.mtWarning,
[System.UITypes.TMsgDlgBtn.mbYesToAll, System.UITypes.TMsgDlgBtn.mbAbort],
System.UITypes.TMsgDlgBtn.mbAbort,
0);
NShowMessageSync(IntToStr(mr));
mr := NMessageDialogSync('3',
System.UITypes.TMsgDlgType.mtInformation,
[System.UITypes.TMsgDlgBtn.mbIgnore, System.UITypes.TMsgDlgBtn.mbAll, System.UITypes.TMsgDlgBtn.mbHelp, System.UITypes.TMsgDlgBtn.mbClose],
System.UITypes.TMsgDlgBtn.mbClose,
0);
NShowMessageSync(IntToStr(mr));
Form1.Fill.Kind := TBrushKind.Solid;
if Form1.Fill.Color = TAlphaColors.Red then
Form1.Fill.Color := TAlphaColors.Blue
else
Form1.Fill.Color := TAlphaColors.Red;
end;
end.
Not very clean, but at least I can use this for a while.

Delphi XE5 Android Indy TIDNotify not executed

I have reduced the code to a simple sample.
IdTCPClient reads a message and should display it in a Memo.
The attached code works fine on Windows, but the DoNotify is not executed on android if PostLog is called from ThC_Receive. If I call PostLog via Button.Click from MainForm it is executed.
Any suggestions?
TIdSync works, but is it recommended?
unit HeaderFooterTemplate;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, IdContext, IdSync,
FMX.Layouts, FMX.Memo, IdThreadComponent, IdBaseComponent, IdComponent,
IdTCPConnection, IdTCPClient, IdGlobal;
type
// ---------------------------------------------------------------------------
TLog = class(TIdNotify)
protected
fMsg: String;
procedure DoNotify; override;
//procedure DoSynchronize; override;
public
class procedure PostLog(const S: String);
end;
// ---------------------------------------------------------------------------
THeaderFooterForm = class(TForm)
Header: TToolBar;
Footer: TToolBar;
HeaderLabel: TLabel;
M_Log: TMemo;
IdTCPClient1: TIdTCPClient;
ThC_Receive: TIdThreadComponent;
Button2: TButton;
procedure Button2Click(Sender: TObject);
procedure IdTCPClient1Connected(Sender: TObject);
procedure ThC_ReceiveRun(Sender: TIdThreadComponent);
private
{ Private declarations }
public
{ Public declarations }
end;
var
HeaderFooterForm: THeaderFooterForm;
implementation
{$R *.fmx}
// -----------------------------------------------------------------------------
{ TLog }
// -----------------------------------------------------------------------------
procedure TLog.DoNotify;
begin
HeaderFooterForm.M_Log.Lines.Append(fMsg);
end;
// -----------------------------------------------------------------------------
class procedure TLog.PostLog(const S: String);
begin
with Create do
begin
try
fMsg := S;
Notify;
except
Free;
Raise;
end;
end;
end;
// -----------------------------------------------------------------------------
procedure THeaderFooterForm.Button2Click(Sender: TObject);
begin
try
IdTCPClient1.Host :='192.168.1.12';
IdTCPClient1.Port := 1001;
IdTCPClient1.Connect;
except
on E: Exception do
begin
TLog.PostLog(trim(e.Message));
Raise;
end;
end;
end;
// -----------------------------------------------------------------------------
procedure THeaderFooterForm.IdTCPClient1Connected(Sender: TObject);
begin
ThC_Receive.Start;
end;
// -----------------------------------------------------------------------------
procedure THeaderFooterForm.ThC_ReceiveRun(Sender: TIdThreadComponent);
var
s: string;
begin
try
s:= (IdTCPClient1.IOHandler.ReadLn(TransmissionSeparator, IndyTextEncoding(IdTextEncodingType.encUTF16LE)));
TLog.PostLog(trim(s));
except
on E: Exception do
begin
TLog.PostLog(trim(e.Message));
Raise;
end;
end;
end;
// -----------------------------------------------------------------------------
end.
Thank you,
Spectro
When called in a worker thread, TIdNotify uses TThread.Queue() and TIdSync uses TThread.Synchronize(), both of which go through the same RTL queue, so it is unlikely that TThread.Synchronize() would work but TThread.Queue() would not. On the other hand, when called in the main thread, TThread is not used (unless TIdNotify.MainThreadUsesNotify is set to true), TIdNotify.DoNotify() and TIdSync.DoSynchronize() are called directly instead. If TIdNotify is not working in a thread, then TThread.Queue() has to be broken, which would be an Embarcadero issue, not an Indy issue.
That being said, TIdNotify does have logic to use TThread.Synchronize() on Delphi versions that do not have TThread.Queue() available. You could try making a copy of IdSync.pas and modify it to undefine the HAS_STATIC_TThread_Queue define on Android, and then add the modified file to your project. That only works when Runtime Packages are disabled, though. I am not an Android developer, so I do not know how Delphi uses Packages on mobile platforms.
BTW, IndyTextEncoding(IdTextEncodingType.encUTF16LE) can be replaced with IndyTextEncoding_UTF16LE.
Update: a bug has been identified in FireMonkey itself (see QC #123579), going all the way back to when FireMonkey was first introduced, and still exists in XE5 Update 2. In short, FMX.TApplication does not assign a handler to the System.Classes.WakeMainThread callback (Vcl.TApplication does). TThread calls WakeMainThread to notify the main thread that a Synchronize/Queue() request is pending. Thus, if the main message loop is idle when Synchronize/Queue() is called, nothing happens until a later time if/when something else puts a new message in the main message queue. Without that, TApplication.Idle() is not called, and so CheckSynchronize() is not called to process pending Synchronize/Queue() requests. That means for background/nonvisual processes, Synchronize/Queue() may never work correctly at all, and sporadically in GUI processes. Until Embarcadero fixes that bug, a workaround would be to either post a custom message to the main message queue to "wake" it up, or else manually call CheckSynchronize() in the main thread periodically, such as in a timer.

TVideoCaptureDevice not calling OnSampleBufferReady on Android

I am developing an app that must run on iOS, Android, and W32/64 using Delphi FireMonkey on RAD Studio XE5. I am trying to access the video camera in order to scan in barcodes.
I've got the example VideoCaptureHD app running on W32 using a USB webcam, but on Android the app just shows the background color, and no image from the camera. I've added a breakpoint to the SampleBufferReady method, but it never gets called.
Also, the capture button is enabled indicating that the camera device is recognized at some level.
//---------------------------------------------------------------------------
// This software is Copyright (c) 2012 Embarcadero Technologies, Inc.
// You may only use this software if you are an authorized licensee
// of Delphi, C++Builder or RAD Studio (Embarcadero Products).
// This software is considered a Redistributable as defined under
// the software license agreement that comes with the Embarcadero Products
// and is subject to that software license agreement.
//---------------------------------------------------------------------------
unit CaptureForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Objects, FMX.Media, FMX.StdCtrls,
FMX.Layouts, FMX.ListBox;
type
TForm1 = class(TForm)
Image1: TImage;
CaptureButton: TSpeedButton;
Layout1: TLayout;
SaveDialog1: TSaveDialog;
Ellipse1: TEllipse;
StopButton: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure CaptureButtonClick(Sender: TObject);
procedure StopButtonClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
VideoCamera: TVideoCaptureDevice;
procedure SampleBufferReady(Sender: TObject; const ATime: TMediaTime);
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.FormCreate(Sender: TObject);
begin
VideoCamera := TCaptureDeviceManager.Current.DefaultVideoCaptureDevice;
if VideoCamera <> nil then
begin
Ellipse1.AnimateFloat('Opacity', 1, 1.0);
VideoCamera.OnSampleBufferReady := SampleBufferReady;
VideoCamera.StartCapture;
end
else
begin
CaptureButton.Enabled := False;
Caption := 'Video capture devices not available.';
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if VideoCamera <> nil then
VideoCamera.StopCapture;
end;
procedure TForm1.SampleBufferReady(Sender: TObject; const ATime: TMediaTime);
begin
VideoCamera.SampleBufferToBitmap(Image1.Bitmap, True);
end;
procedure TForm1.StopButtonClick(Sender: TObject);
begin
if VideoCamera <> nil then
begin
if VideoCamera.State = TCaptureDeviceState.Capturing then
begin
Ellipse1.AnimateFloat('Opacity', 0, 1.0);
StopButton.Text := 'Capture';
VideoCamera.StopCapture;
end
else
begin
Ellipse1.AnimateFloat('Opacity', 1, 1.0);
StopButton.Text := 'Stop';
VideoCamera.StartCapture
end;
end;
end;
procedure TForm1.CaptureButtonClick(Sender: TObject);
begin
if SaveDialog1.Execute then
Image1.Bitmap.SaveToFile(SaveDialog1.FileName);
end;
end.
I can't find bug or mistake in your application.
You should also check if in Project->Options->UsesPermisions->Android->Camera if is True.
I did similar application and it works (public variables the same as yours):
procedure TForm1.Button1Click(Sender: TObject); //starts capturing on click
begin
VideoCamera.StartCapture;
end;
procedure TForm1.Button2Click(Sender: TObject); //stops capturing on click
begin
VideoCamera.StopCapture;
end;
procedure TForm1.Button3Click(Sender: TObject); //create camera on click
begin
VideoCamera := TCaptureDeviceManager.Current.DefaultVideoCaptureDevice;
if VideoCamera <> nil then
begin
VideoCamera.OnSampleBufferReady := SampleBufferReady;
end
else
begin
ShowMessage('Video capture devices not available.');
end;
end;
procedure TForm1.SampleBufferReady(Sender: TObject; const ATime: TMediaTime); // copy to bitmap works perfect
begin
VideoCamera.SampleBufferToBitmap(Image1.Bitmap, True);
end;

Categories

Resources