I want to ping to a server with indy component TidIcmpClient in Android plattform but the debugger stops the code with a "Socket Error #1". The code is in a separate thread, so i post here the thread code:
procedure TEco.Execute;
var
contadoreco: Cardinal;
buffer: string;
begin
buffer:='12345678901234567890123456789012';
eco:=TIdIcmpClient.Create(nil);
for contadoreco:=1 to 4 do
begin
with eco do
begin
ReceiveTimeout:=2000;
Host:=servidor;
PacketSize:=32;
Ping(buffer,contadoreco);
ipservidor:=ReplyStatus.FromIpAddress;
end;
Synchronize(procedure
begin
Form1.StringGrid1.Cells[0,contadoreco]:=ipservidor;
Form1.StringGrid1.Cells[1,contadoreco]:=IntToStr(tiempoeco);
end);
end;
eco.Free;
end;
The TEco object is declared here:
TEco = class(TThread)
private
servidor: string;
eco: TIdIcmpClient;
terminado: Boolean;
tiempoeco: Cardinal;
ipservidor: string;
protected
procedure Execute; override;
end;
How i can ping a server with TidIcmpClient in Android? Am i doing something wrong? Superuser rights or some so? Thanks in advance for help me, and sorry for poor english. I expect you understand my question ;) :)
TIdIcmpClient uses a RAW socket, which requires admin/root access on most systems, including Android.
There are two ways to perform a ping in Android using Android's own APIs:
use the isReachable() method of the InetAddress class. However, apparently this does not work correctly.
Use java.lang.ProcessBuilder() to spawn /system/bin/ping. In fact, the java.lang.Process documentation shows an example of that. The downside is that you would have to manually parse the output.
Either solution would require you to use Delphi's JNI wrapper to access the relevant Android APIs.
Related
I'm using Delphi 10.3 Community Edition and want to use the WRITE_SETTINGS in my application to set the brightness.
I could get it managed to implement this procedure to call the settings dialog:
procedure RequestWriteSettings;
var
Intent: JIntent;
begin
Intent := TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_MANAGE_WRITE_SETTINGS);
TAndroidHelper.Activity.startActivity(Intent);
end;
I can call this procedure in my application, the dialog appears and I can set the necessary permissions.
But I don't want to call this procedure permanently, because that's not user friendly.
I need to check if the WRITE_SETTINGS permission is already set, but I don't know how to implement this in Delphi/Firemonkey.
What I could find is that one has to call the "Settings.System.canWrite(context)" function, but I only can find samples for java.
Calling these kind of java routines in Delphi isn't that easy. I'm searching around already for some weeks and tried "things on my own", but still without success.
Can someone provide the code line how this routine has to be called in Delphi?
Thanks so much in advance!
MPage
Example code for checking WRITE_SETTINGS:
uses
Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Provider, Androidapi.JNI.Net, Androidapi.Helpers;
procedure TForm1.RequestWriteSettingsButtonClick(Sender: TObject);
begin
if not TJSettings_System.JavaClass.canWrite(TAndroidHelper.Context) then
StartWritePermissionsActivity
else
ShowMessage('System says app can write settings');
end;
procedure TForm1.StartWritePermissionsActivity;
var
LIntent: JIntent;
begin
LIntent := TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_MANAGE_WRITE_SETTINGS);
LIntent.setData(TJnet_Uri.JavaClass.parse(StringToJString('package:').concat(TAndroidHelper.Context.getPackageName)));
TAndroidHelper.Context.startActivity(LIntent);
end;
In the meanwhile I found a solution for myself, but I think Dave's is better. ;-)
That's what I found with the "trial and error" method:
function HasWriteSettings: Boolean;
begin
// Call canWrite to check for permission WRITE_SETTINGS
Result := TJSettings_System.JavaClass.canWrite(TAndroidHelper.Context.getApplicationContext);
end;
This code is working in a Firemonkey Windows app, but doesn't work in Android app, i get Goodbye instead of Welcome, what is wrong?
Edit8 Text : 162496 //Computer unique code
Edit9 Text : 1564224593 //serial #
procedure TForm2.Button5Click(Sender: TObject);
var
f2,f1:textfile;
i,j:byte;
s1,s2,s3,c:string;
F: TextFile;
begin
j:=0;
s2 := Edit8.Text;
for i:=1 to Length(s2) do
if (s2[i]>='0') and (s2[i]<='9') then
s3:=s3+s2[i];
for i:=1 to Length(s3)-1 do
if (edit9.Text[i*2-1]<>s3[i]) or (abs(strtoint(s3[i+1])-strtoint(s3[i]))<> strtoint(edit9.Text[i*2])) then
inc(j);
if j=0 then
ShowMessage('Welcome')
else
ShowMessage('Goodbye');
end;
Delphi mobile compilers use zero-based strings.
You have three choices:
As #Günter_the_Beautiful points out, your best choice is to rewrite your code to use string helpers (which are always 0-based)
Rewrite your code to use 0-based indexing: for I := 0 to ...
If you need a quick fix, turn it off locally for your code snippet using the {$ZEROBASEDSTRINGS OFF} directive (and revert it back with {$ZEROBASEDSTRINGS ON} again).
For options 2. and 3., if you need your code to be cross-platform, consider using appropriate platform conditional defines. This is what makes option 1. compelling: no need to clutter your code with conditional defines.
I am using these two helper routines:
FUNCTION GetChar(CONST S : STRING ; OneBasedIndex : LongWord) : CHAR;
BEGIN
{$IF CompilerVersion>=24 }
Result:=S[SUCC(OneBasedIndex-LOW(S))]
{$ELSE }
Result:=S[OneBasedIndex]
{$IFEND }
END;
PROCEDURE SetChar(VAR S : STRING ; OneBasedIndex : LongWord ; NewChar : CHAR);
BEGIN
{$IF CompilerVersion>=24 }
S[SUCC(OneBasedIndex-LOW(S))]:=NewChar
{$ELSE }
S[OneBasedIndex]:=NewChar
{$IFEND }
END;
This way, you can continue working with strings as 1-based (which is the logical choice :-)) as long as you always access the strings as characters using these two functions.
In my app, developed with XE7 for Android/iOS, I have a form for scanning barcodes. Upon a found barcode, my app validates whether it is an acceptable barcode or not. Following tutorials here: http://www.fmxexpress.com/qr-code-scanner-source-code-for-delphi-xe5-firemonkey-on-android-and-ios/
Currently I am testing on Android and I am able to integrate scanning and reading of barcodes, but the 'onBarCode' event does not fire when returned from the shared Activity of finding the barcode. Same code worked well with previous versions of Rad Studio ( XE4, XE5, XE6) but now in XE7 it does not.
Here are some snippets of code:
...
begin
Scanner := TAndroidBarcodeScanner.Create(true);
Scanner.OnBarCode := BarcodeHandler;
Scanner.Scan;
end;
procedure TmScannerForm.BarcodeHandler(Sender: TAndroidBarcodeScanner;
BarCode: String);
begin
text1.Text := Barcode;
memo1.PasteFromClipboard;
AddBarcode(BarCode, true);
end;
AddBarCode is the even I used to validate and add barcode to a list, but I didnt include it, because that code isn't the problem - it's not even triggering. The Text1.text:=Barcode and memo1.paseFromClipboard were in their for validating the even wasn't firing too. I can confirm the barcodes are being read because if I tap and manually paste, the barcode shows.
Why is this not working on XE7 as it did in previous versions of Rad Studio?
Andrea Magni has a more elegant solution than the timer on his blog based on event handling.
I would comment to send the link but I don't have enough reputation.
The link to his blog is :
http://blog.delphiedintorni.it/2014/10/leggere-e-produrre-barcode-con-delphi.html
Maybe this can help you. The blog is in Italian though but the sources are provided and are explaining by themselves.
There is a source code fragment on http://john.whitham.me.uk/xe5/ which looks useable (based on Zxing):
intent := tjintent.Create;
intent.setAction(stringtojstring('com.google.zxing.client.android.SCAN'));
sharedactivity.startActivityForResult(intent,0);
The code in the linked article also shows how to receive the Intent result. (I don't work with Delphi on Android so I am not sure if that part uses a best practice - TTKRBarCodeScanner uses a workaround with a Timer and the Clipboard).
I would try this as an alternative to see if works.
This code works to me!
Set timer enabled to true when you run your scan code
procedure Tform.Timer1Timer(Sender: TObject);
begin
if (ClipService.GetClipboard.ToString <> '') then
begin
timer1.Enabled:=false;
zSearch.Text := ClipService.GetClipboard.ToString;
//Do what you need
end;
end;
This code to me works fine!
in andorid.BarcodeScanner
function TAndroidBarcodeScanner.HandleAppEvent(AAppEvent: TApplicationEvent;
AContext: TObject): Boolean;
var
aeBecameActive : TApplicationEvent;
begin
aeBecameActive := TApplicationEvent.BecameActive;
if FMonitorClipboard and (AAppEvent = aeBecameActive) then
begin
GetBarcodeValue;
end;
end;
I'm trying to use Indy's TIdHTTP component to send data to a website.
The code works perfectly on Windows platform but unfortunately it behaves strangely on Android platform. The problem occurs when I use TIdMultipartFormDataStream to send POST parameters.
On Android platform TIdMultipartFormDataStream behaves strangely and this what happens:
Suppose your POST data is myparam=myvalue where "myparam" is parameter name and "myvalue" is the parametervalue.
The parameter values gets changed to {FIRST CHARACTER REMOVED}yvalue{NULL CHARACTER} so the final output will look like this yvalue\x00 where \x00 is a null character.
I can replace TIdMultipartFormDataStream with TStringList and I won't face such issue but I prefer to use TIdMultipartFormDataStream because it enables me to upload files + send POST data at the same time.
Sample code:
procedure HTTPPOST;
var
HTTP: TIdHTTP;
POSTData: TIdMultipartFormDataStream;
begin
HTTP := TIdHTTP.Create(nil);
POSTData := TIdMultipartFormDataStream.Create;
try
POSTData.AddFile('myfile','file.txt'); // works
POSTData.AddFormField('username', 'user1'); // On Android the value gets changed to ser1\x00 where \x00 = Null character
HTTP.Post('http://www.example.com', POSTData)
finally
POSTData.Free;
end;
end;
Note: the code was tested using Delphi XE5 and Delphi XE5 Update 1
What you describe sounds like a ZEROBASEDSTRINGS bug, which should not exist in the current SVN version because Indy disables ZEROBASEDSTRINGS globally in all of its units after it kept suffering from lots of ZBS bugs in XE4. So I do suggest you upgrade to the lastest SVN verson. If you are having problems doing so, please update your question with details explaining why
Works..
procedure HTTPPOST;
var
HTTP: TIdHTTP;
POSTData: TIdMultipartFormDataStream;
begin
HTTP := TIdHTTP.Create(nil);
POSTData := TIdMultipartFormDataStream.Create;
try
POSTData.AddFile('myfile','file.txt'); // works
POSTData.AddFormField('username', UTF8Encode('user1'), 'utf-8').ContentTransfer:= '8bit';
HTTP.Post('http://www.example.com', POSTData)
finally
POSTData.Free;
end;
end;
I have an Android application communicating with a Delphi 2006 web service application using Indy 10 TIdHttpServer (coming with Delphi 2006). The Delphi application generates a big XML file and serves this. The XML generation may last more than 5 minutes.
If the duration of GenerateXml() is more than about 5 minutes (*), I detect an error 10053 in TIdHTTPResponseInfo.WriteContent if running in the Delphi IDE:
Socket Error # 10053 Software caused connection abort.
However, on the android side nothing is detected and the HttpGet-call lasts forever.
My questions are:
1.) Why do I get the error 10053 and how can I avoid it? It seems like android times out the connection, but http.socket.timeout is set to infinite.
and
2.) What can I do to detect such an error on the client side (other than setting timeout, which would have to be too big to be useful)? Can I do something in TIdHttpServer.OnException?
Here is my code.
Android - download function, which is run inside an AsyncTask:
protected static HttpEntity downloadEntity(String url) throws IOException {
HttpClient client = new DefaultHttpClient();
//Check because of Error 10053: but timeout is null -> infinite
Log.d("TAG", "http.socket.timeout: " + client.getParams().getParameter("http.socket.timeout"));
HttpGet get = new HttpGet(url);
HttpResponse response;
try {
//in case of Error 10053 the following call seems to last forever (in PlainSocketImpl.read)
response = client.execute(get);
} catch (ClientProtocolException e) {
//...
}
//...
return response.getEntity();
}
Delphi implementation of TIdHttpServer.OnCommandGet:
procedure ServeXmlDoc(XmlDoc: IXMLDocument; ResponseInfo: TIdHTTPResponseInfo);
var
TempStream: TMemoryStream;
begin
ResponseInfo.ContentType := 'text/xml';
TempStream := TMemoryStream.Create;
XMLDoc.SaveToStream(TempStream);
ResponseInfo.FreeContentStream := True;
ResponseInfo.ContentStream := TempStream;
end;
procedure TMyService.HTTPServerCommandGet(AContext: TIdContext; RequestInfo: TIdHTTPRequestInfo;
ResponseInfo: TIdHTTPResponseInfo);
begin
Coinitialize(nil);
try
//...
ServeXmlDoc(GenerateXml(), ResponseInfo);
finally
CoUninitialize;
end;
end;
Edit: (*) I have done further testing and experienced the error even in cases where the whole process had a duration of under 2 minutes.
Something between Android and your server, such as a firewall/router, is likely cutting the connection after it is idle for too long. You should try enabling TCP keep-alives to avoid that.
On the other hand, this is the kind of situation that HTTP 1.1's chunked transfer encoding was designed to handle (assuming you are using HTTP 1.1 to begin with). Instead of waiting 5 minutes for the entire XML to be generated in full before then sending it to the client, you should send the XML in pieces as they are being generated. Not only does that keep the connection active, but it also reduces the server's memory footprint since it doesn't have to store the entire XML in memory at one time.
TIdHTTPServer does not (yet) natively support sending chunked responses (but TIdHTTP does support receiving chunked responses), however it would not be very difficult to implement manually. Write a custom TStream derived class and overwrite its virtual Write() method (or use Indy's TIdEventStream class) to write data to the HTTP client using the format outlined in RFC 2616 Section 3.6.1. With that, you can have ServeXmlDoc() set the ResponseInfo.TransferEncoding property to 'chunked' and call the ResponseInfo.WriteHeader() method without setting either the ResponseInfo.ContentText or ResponseInfo.ContentStream properties, then pass your custom stream to IXMLDocument.SaveToStream() so it will finish writing the response data after the headers. For example:
type
TMyChunkedStream = class(TStream)
private
fIO: TIdIOHandler;
public
constructor Create(AIO: TIdIOHandler);
function Write(const Buffer; Count: Longint): Longint; override;
procedure Finished;
...
end;
constructor TMyChunkedStream.Create(AIO: TIdIOHandler);
begin
inherited Create;
fIO := AIO;
end;
function TMyChunkedStream.Write(const Buffer; Count: Longint): Longint; override;
begin
if Count > 0 then
begin
fIO.WriteLn(IntToHex(Count, 1));
fIO.Write(RawToBytes(Buffer, Count));
fIO.WriteLn;
end;
Result := Count;
end;
procedure TMyChunkedStream.Finished;
begin
fIO.WriteLn('0');
fIO.WriteLn;
end;
procedure ServeXmlDoc(XmlDoc: IXMLDocument; ResponseInfo: TIdHTTPResponseInfo);
var
TempStream: TMyChunkedStream;
begin
ResponseInfo.ContentType := 'text/xml';
ResponseInfo.TransferEncoding := 'chunked';
ResponseInfo.WriteHeader;
TempStream := TMyChunkedStream.Create(ResponseInfo.Connection.IOHandler);
try
XMLDoc.SaveToStream(TempStream);
TempStream.Finished;
finally
TempStream.Free;
end;
end;
If, on the other hand, the bulk of your waiting is inside of GenerateXml() and not in XmlDoc.SaveToStream(), then you need to rethink your server design, and figure out a way to speed up GenerateXml(), or just get rid of IXMLDocument and create the XML manually so you can send it using the ResponseInfo.Connection.IOHandler as you are creating the XML content.