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.
Related
I need get telephone number of device on which my application runing. If has device two SIM cards ideal to get both numbers or if SIM card is not inserted (tablet device) can detect this.
I found some JAVA code but I have no idea how translate it to Delphi
TelephonyManager phneMgr = (TelephonyManager)mAppContext.getSystemService(Context.TELEPHONY_SERVICE);
String phneNmbr = phneMgr.getLine1Number();
I try write something but it not working ....
USES Androidapi.Helpers, Androidapi.JNI.JavaTypes, Androidapi.JNI.Telephony;
procedure TForm1.Button1Click(Sender: TObject);
var
num: JString;
tman: Androidapi.JNI.Telephony.JTelephonyManager;
begin
tman:=TJtelephonyManager.Create;
num := tman.getLine1Number;
edit1.Text:=Jstringtostring(num);
end;
Something like this should do it, based on experience with other system services. This translates what you have suggested is viable Java code.
I'll edit this to make it compile correctly (if there are any issues with it) when I have a copy of Delphi to hand later, but this is roughly what is required.
Note that quick look at the telephony manager documentation doesn't readily say how one would get the phone number for a second SIM, but it does translate what you were trying to translate.
uses
System.SysUtils,
Androidapi.Helpers,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.Telephony;
function DeviceTelephoneNumber: string;
var
TelephonyManagerObj: JObject;
TelephonyManager: JTelephonyManager;
begin
TelephonyManagerObj:= TAndroidHelper.Context.getSystemService(
TJContext.JavaClass.TELEPHONY_SERVICE);
if TelephonyManagerObj <> nil then
begin
TelephonyManager := TJTelephonyManager.Wrap(TelephonyManagerObj);
if TelephonyManager <> nil then
Result := JStringToString(TelephonyManager.getLine1Number);
end;
end;
This code is also a possibility, which works in Android 5.1 and later.
function DeviceTelephoneNumbers: TArray<string>;
var
SubscriptionManager: JSubscriptionManager;
I, SubscriptionInfoCount: Integer;
SubscriptionInfoList: JList;
SubscriptionInfo: JSubscriptionInfo;
begin
// Subscription manager is only available in Android 5.1 and later
if TOSVersion.Check(5, 1) then
begin
SubscriptionManager := TJSubscriptionManager.JavaClass.from(
TAndroidHelper.Context);
SubscriptionInfoCount := SubscriptionManager.getActiveSubscriptionInfoCount;
SubscriptionInfoList := SubscriptionManager.getActiveSubscriptionInfoList;
SetLength(Result, SubscriptionInfoCount);
for I := 0 to Pred(SubscriptionInfoCount) do
begin
SubscriptionInfo := TJSubscriptionInfo.Wrap(SubscriptionInfoList.get(I));
if SubscriptionInfo <> nil then
Result[I] := JStringToString(SubscriptionInfo.getNumber);
end;
end
else
begin
// If running on older OS, use older API
SetLength(Result, SubscriptionInfoCount);
Result[0] := DeviceTelephoneNumber
end;
end;
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.
Typically in a Delphi VCL application which uses a TDataset descendent as data storage (eg TClientDataset), in the Dataset1BeforeDelete handler we do something like this:
procedure TClientModule1.MyCDSBeforeDelete(DataSet: TDataSet);
begin
if MessageDlg('Delete?', mtCOnfirmation, [mbyes, mbNo], 0) <> mrYes then
SysUtils.Abort
end;
Now in a FMX application designed to run on Android, this becomes:
procedure TClientModule1.MyCDSBeforeDelete(DataSet: TDataSet);
MessageDlg('Delete?'
,
TMsgDlgType.mtWarning, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], 0,
procedure(const AResult: TModalResult)
begin
if AResult <> mrYes then
Abort;
end
);
end;
Except, that's not going to work! The messagedlg is going to hold the user's attention, but the event handler code is going to continue and allow the record to be deleted anyway.
What's the solution?
Because modal window and Message Box currently are not supported in FMX for Android you should use some kind of "dog-nail" solution
Ad-Hoc solution #1, .
In main form or in form which should open Modal window write code like:
procedure TForm1.btnSelectClick(Sender: TObject);
begin
if fmSelect = nil then
begin
Application.CreateForm(TfmSelect, fmSelect);
fmSelect.Callback := Yahoo;
end;
fmSelect.Show;
end;
procedure TForm1.Yahoo(ASelectedItem: String);
begin
ShowMessage(ASelectedItem);
end;
in fmSelect should be your message and buttons with options (like Yes, No, May be, Not today).
in fmSelect form you should declare PUBLIC variable Callback: TCallback;
Once user press some button, you should call this function and close form:
procedure TfmSelect.btnSelectClick(Sender: TObject);
begin
if Assigned(Callback) then
Callback('user press button XXX');
Close;
end;
TCallback just regular function which return String type (you could change it to Integer).
TCallback = procedure (ASelected: String) of object;
Ad-Hoc solution #2
simulat to first, but with using hidden TComboBox. In combobox items will be stored all options, like "Yes", "No", "Maybe tomorrow". Once ComboBox was closed OnClosePopup event, you get value of user choise.
3. Take a look how it's done somewhere in Embarcadero samples (from XE8):
http://docwiki.embarcadero.com/RADStudio/XE8/en/Mobile_Tutorial:_Using_FireDAC_and_SQLite_%28iOS_and_Android%29
So in your case will be
private
procedure FCloseDialogProc(const AResult: TModalResult);
procedure TForm1.Button1Click(Sender: TObject);
begin
MessageDlg('Want something', TMsgDlgType.mtWarning, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], 0, FCloseDialogProc);
end;
procedure TForm1.FCloseDialogProc(const AResult: TModalResult);
begin
Label1.Text := IntToStr(AResult);
// -1 -- click outside
// 6 -- yes
// 7 -- no
end;
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.
I have a TIdUDPServer on a form setup with a default port and broadcast mode enabled and cannot get it to receive anything under Android.
The same code is working fine under iOS but if I retarget Android I do not receive anything.
Is there a trick I am missing. I have checked the "User Permissions" and turn on everything I think might influence this but have had no luck with getting it working.
Thanks in advance, Martin
Is your device connected to WiFi or a mobile network? UDP does not work over mobile unless you initiate an outgoing UDP connection first to open up the mobile's carrier's firewall.
Are the UDP packets being sent directly to the device's IP or to a broadcast IP? By default, Android discards UDP packets that are not addressed directly to the device's IP. For multicast packets, WifiManager.createMulticastLock() must been called beforehand to allow such packets, and you need to include the CHANGE_WIFI_MULTICAST_STATE permission in the app's manifest. That might apply to UDP broadcasts as well, I am not sure.
Thank you Remy for putting me on the right track.
Here is the complete code to receive broadcast UDP packets...
unit Androidapi.JNI.WiFiManager;
interface
uses
Androidapi.JNIBridge, Androidapi.Jni, androidapi.JNI.JavaTypes, androidapi.JNI.Net,
androidapi.JNI.Os, FMX.Helpers.Android, Androidapi.JNI.GraphicsContentViewText, SysUtils;
Type
JWiFiManager = interface; // android/net/wifi/WifiManager
JMulticastLock = interface; // android/net/wifi/WifiManager$MulticastLock
JWiFiManagerClass = interface(JObjectClass)
['{F69F53AE-BC63-436A-8F69-57389B30CAA8}']
function getSystemService(Contex: JString): JWiFiManager; cdecl;
end;
[JavaSignature('android/net/wifi/WifiManager')]
JWiFiManager = interface(JObject)
['{382E85F2-6BF8-4255-BA3C-03C696AA6450}']
function createMulticastLock(tag: JString): JMulticastLock;
end;
TJWiFiManager = class(TJavaGenericImport<JWiFiManagerClass, JWiFiManager>) end;
JMulticastLockClass = interface(JObjectClass)
['{C0546633-3DF2-46B0-8E2C-C14411674A6F}']
end;
[JavaSignature('android/net/wifi/WifiManager$MulticastLock')]
JMulticastLock = interface(JObject)
['{CFA00D0C-097C-45E3-8B33-0E5A6C9FB9F1}']
procedure acquire();
function isHeld(): Boolean;
procedure release();
procedure setReferenceCounted(refCounted: boolean);
end;
TJMulticastLock = class(TJavaGenericImport<JMulticastLockClass, JMulticastLock>) end;
function GetWiFiManager: JWiFiManager;
implementation
function GetWiFiManager: JWiFiManager;
var
Obj: JObject;
begin
Obj := SharedActivityContext.getSystemService(TJContext.JavaClass.WIFI_SERVICE);
if not Assigned(Obj) then
raise Exception.Create('Could not locate Wifi Service');
Result := TJWiFiManager.Wrap((Obj as ILocalObject).GetObjectID);
if not Assigned(Result) then
raise Exception.Create('Could not access Wifi Manager');
end;
And then to acquire the necessary lock...
wifi_manager := GetWiFiManager;
multiCastLock := wifi_manager.createMulticastLock(StringToJString('LightFactory Remote'));
multiCastLock.setReferenceCounted(true);
multiCastLock.acquire;
Finally dont forget the permissions mentioned in Remy's post.