Sunday, November 3, 2013

How to create Android services using Delphi XE5 using TTimer

Delphi XE5 does not provide the ability to create Android services, yet. However, there is a simple workaround: If you put a TTimer on a form, this form and this TTimer will also continue to run after switching to another app, or after closing the screen. This way, you can make a simple app that does something even when not in use.

Saturday, November 2, 2013

Delphi for Android uses 0-based strings

Delphi handles strings differently when compiling for Windows and Android.

Windows has 1-based strings, i.e. the first character in a string is s[1].

Android has 0-based strings, i.e. the first character in a string is s[0].

Also, you should expect, that future versions of Delphi will not allow you to write this code:

s[1]:='a';

Android apps start slowly? Here is the trick

When you run an Android app, it will be a slow startup because of the Android technology choices. The local emulator is an emulator, which means slow. If you attach a phone for development, the app still needs to be packaged, the old app needs to be uninstalled, the new version needs to be installed etc. All this takes time.

Fortunately, there is a solution: You can make your app compile for both Android and Windows, and debug most functionality as a Windows app. These guidelines can help you:

  1. Put all Android-specific code into units that have a name that starts with "Android". For instance, if you want to send an SMS, put the code for that into an AndroidSms.pas unit.
  2. Use {$IFDEF ANDROID} for code that you want to execute only when deployed on Android.
  3. Exclude Android* units from the dpr file
Here is an example of a uses-clause that compiles with both Android and Windows:

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
{$IFDEF ANDROID}
  AndroidSms,
{$ENDIF}
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls;

Here is an example of some code that does one thing on Android, and another thing when compiling to Windows:

procedure TForm2.SendSMS (target,messagestr:string);
begin
  {$IFDEF ANDROID}
  AndroidSms.SendSms(target,messagestr);
  {$ELSE}
  Memo1.Lines.Add (target+': '+messagestr);
  {$ENDIF}
end;

As you can see in this last example, the Windows version will visualize to the programmer, that the app was meant to send an SMS at this point, instead of actually doing it. This speeds up testing a lot.

Friday, November 1, 2013

Fetching files using https (SSL) using Delphi for Android

Copy & paste solution, which logs in, supports https and returns the result as one large string:

uses
  Classes, SysUtils,
  IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP,
  IdSSLOpenSSL;

function HttpsAuthGet(Url, Username, Password: string):string;
var
  ssl: TIdSSLIOHandlerSocketOpenSSL;
  http: TIdHTTP;
begin
  http := TIdHttp.Create(nil);
  try
    ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
    try
      http.IOHandler := ssl;
      http.Request.BasicAuthentication:=True;
      http.Request.Username := Username;
      http.Request.Password := Password;
      Result:=http.Get(Url);
      if (http.ResponseCode<200) or (http.ResponseCode>=300) then
        raise Exception.Create(http.ResponseText);
    finally
      FreeAndNil(ssl);
    end;
  finally
    FreeAndNil(http);
  end;
end;

Download file to Android app using Delphi

This is a very simple one, because it works the same way as it did with Delphi for Windows:

function HttpGet(const url: string): string;
var
  HTTP: TIdHTTP;
begin
  HTTP := TIdHTTP.Create(nil);
  try
    Result:=http.Get(url);
  finally
    FreeAndNil (http);
  end;
end;

Where did PAnsiChar go with Delphi XE5 and Android?

Olaf Monien has written about this here.

Sunday, October 27, 2013

How to fetch SMS messages from the Android inbox using Delphi

The code speaks for itself:

uses
  SysUtils,
  FMX.Helpers.Android,
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.Net,
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.Telephony;

function FetchSms:string;
var
  cursor: JCursor;
  uri: Jnet_Uri;
  address,person,msgdatesent,protocol,msgread,msgstatus,msgtype,
  msgreplypathpresent,subject,body,
  servicecenter,locked:string;
  msgunixtimestampms:int64;
  addressidx,personidx,msgdateidx,msgdatesentidx,protocolidx,msgreadidx,
  msgstatusidx,msgtypeidx,msgreplypathpresentidx,subjectidx,bodyidx,
  servicecenteridx,lockedidx:integer;
begin
  uri:=StrToJURI('content://sms/inbox');
  cursor := SharedActivity.getContentResolver.query(uri, nil, nil,nil,nil);
  addressidx:=cursor.getColumnIndex(StringToJstring('address'));
  personidx:=cursor.getColumnIndex(StringToJstring('person'));
  msgdateidx:=cursor.getColumnIndex(StringToJstring('date'));
  msgdatesentidx:=cursor.getColumnIndex(StringToJstring('date_sent'));
  protocolidx:=cursor.getColumnIndex(StringToJstring('protocol'));
  msgreadidx:=cursor.getColumnIndex(StringToJstring('read'));
  msgstatusidx:=cursor.getColumnIndex(StringToJstring('status'));
  msgtypeidx:=cursor.getColumnIndex(StringToJstring('type'));
  msgreplypathpresentidx:=cursor.getColumnIndex(StringToJstring('reply_path_present'));
  subjectidx:=cursor.getColumnIndex(StringToJstring('subject'));
  bodyidx:=cursor.getColumnIndex(StringToJstring('body'));
  servicecenteridx:=cursor.getColumnIndex(StringToJstring('service_center'));
  lockedidx:=cursor.getColumnIndex(StringToJstring('locked'));
  while (cursor.moveToNext) do begin
    address:=JStringToString(cursor.getString(addressidx));
    person:=JStringToString(cursor.getString(personidx));
    msgunixtimestampms:=cursor.getLong(msgdateidx);
    msgdatesent:=JStringToString(cursor.getString(msgdatesentidx));
    protocol:=JStringToString(cursor.getString(protocolidx));
    msgread:=JStringToString(cursor.getString(msgreadidx));
    msgstatus:=JStringToString(cursor.getString(msgstatusidx));
    msgtype:=JStringToString(cursor.getString(msgtypeidx));
    msgreplypathpresent:=JStringToString(cursor.getString(msgreplypathpresentidx));
    subject:=JStringToString(cursor.getString(subjectidx));
    body:=JStringToString(cursor.getString(bodyidx));
    servicecenter:=JStringToString(cursor.getString(servicecenteridx));
    locked:=JStringToString(cursor.getString(lockedidx));
    Result:=IntToStr(trunc(msgunixtimestampms/1000))+' '+address+' '+body;
  end;
end;

How to send SMS with Delphi on Android

Remember to go into Project/Options/Permissions and allow the app to send SMS. The code is:

uses
  FMX.Helpers.Android,
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.Net,
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.Telephony;

procedure SendSMS (target,messagestr:string);
var
  smsManager: JSmsManager;
  smsTo: JString;
begin
  smsManager:= TJSmsManager.JavaClass.getDefault;
  smsTo:= StringToJString(target);
  smsManager.sendTextMessage(smsTo, nil, StringToJString(messagestr), nil, nil);
end;

Getting started with Delphi XE5 and Android

This is a list of the problems that I experienced when I tried to use Delphi XE5 to make Android apps first time:

  1. My internet connection was not stable, so the Android NDK/SDK was not installed properly. This meant that I could not compile my source code. The solution was to run the "Android Tools" application which makes it easy to finish the installation.
  2. My Google Nexus 7 tablet could not be put into developer mode. The reason was that you need to tap the version number in the settings/about about 10 times before the device converts itself into a developer-device, which has developer settings.
  3. My Google Nexus S and Google Nexus 7 both connected well but in order to make Delphi XE5 run apps on it, you need to install the USB ADB driver. The "Android Tools" installs it on your harddisk, but not in Windows. I found a video on youtube that explained how to install the USB ADB driver, and how to configure Delphi XE5 to deploy to it.
  4. Finally, when all seemed right, it still did not deploy a sample Firemonkey app for mobile - instead, when running aadp with a huge command line parameter list, it returned "Error 1". The reason was, that you need to go into project/options/application and specify an application icon. Once I did that, the app deployed and started running on the phone.