Brian Long Consultancy & Training Services
Ltd.
April 2014
There is an update to this article for Delphi XE6 available here.
Accompanying source files available through this download
link.
Delphi Android apps can use Android APIs, both those already present in the RTL and others we can translate manually, in order to interact with parts of the Android system. This article will look at how we can launch a variety of functionality using standard Android intent objects to instigate execution of available activities. Some of these may be part of the OS, but others will not be. Doing so will enable our own applications to integrate with the Android infrastructure and take advantage of convenient functionality installed on the device.
Through this article you should gain an insight into the techniques involved in communicating with Android activities, both launching them with appropriate parameters, but also receiving responses back from them when they are done.
As mentioned above we'll use the Android Intent
object, which is
an object that represents a message of sorts. We'll set the object up and use it
to launch a variety of different activities in order to bring functionality present
in the Android eco-system 'into' our application. We'll look at displaying battery
usage, viewing URLs, looking at maps, sending SMS and email messages, and scanning
barcodes.
Since we're looking at Android-specific things in this article it seems appropriate to make use of Android-specific features. Rather than showing messages with message boxes, we'll use Android toast messages instead. Toast messages are messages that just pop up onto the screen, rather like toast does out of a toaster. The toast API is not wrapped up by Delphi, but it's a simple API so I include my toast import unit below:
The ins and outs of using the Java Bridge interface to represent Java classes using
Delphi interfaces (one for the class methods and one for the interface methods,
bound together by the TJavaGenericImport
-based generic bridge class)
are a bit beyond the scope of this particular article, but I did go into it in
my CodeRage 8 session on using Android APIs from Delphi XE5:
Even if we ignore the details of the listing you'll see that the specifics of the
toast message, including invoking the toast in the Java UI thread as opposed to
the Delphi FireMonkey thread, have been tucked away and hidden behind a simple
Toast
procedure, which we'll use in the sample code. The toast import
unit has been named similar to how the Delphi RTL's Android import units are named
with an Androidapi.JNI. prefix.
Ok, let's take a simple example to start with. Let's look at how your application
can launch the standard system battery usage activity. You'll see over in the Android API documentation that ACTION_POWER_USAGE_SUMMARY
is a Java string constant defined within the Android Intent
class,
and is designed specifically for this purpose. This string defines the action that
the intent represents, the launching of the battery usage summary activity. So we
need to create an Android Intent
object, tell it that its action is
ACTION_POWER_USAGE_SUMMARY
and then start the activity thus represented.
This can be done with this code:
An Android Intent
is represented in Delphi by the JIntent
interface, which is returned after constructing an instance of the Android Intent
class through the TJIntent
bridge class's init
method.
init
is a class method that represents a Java constructor, and this particular overload takes a Java
string as an action parameter.
So the bridge class offers up the class methods (including constructors) of the
class it represents (Intent
in this case) through its JavaClass
property. This constructor call returns us a JIntent
interface, through
which we can access the instance methods of our constructed Intent
instance.
If you spend any time browsing the Android SDK documentation then do bear in mind that what we call class methods Java programmers call static methods.
The second statement calls onto an Android helper method to give us an interface
reference that represents the FMX activity that sits behind the UI of our Android
app. SharedActivity
gives us a JActivity
reference to
represent the app's activity (although in truth the Delphi app uses an activity
descendant called NativeActivity
as the base class that it inherits
from, which is common practice in a native code application). Through the activity
reference we gain access to the device's PackageManager
, which can help us understand if there
is an installed package that will be able to process the intended action.
Assuming it looks like something will understand the request we ask our activity
to start the activity represented by the Intent
object.
It's quite straightforward stuff really and represents standard Android API programming, just represented in Delphi using the relevant bridge classes and interfaces that are necessary, most of which are provided in various RTL units, but some of which have been added in (for the toast API).
Here's the battery usage display on a HTC One:
It is a common requirement to show a web page from an app. You could embed a browser component to do this or alternatively just ask the system browser to run the URL by requesting the system start an activity that can view the URL in question.
In this case we need to specify an action to view some data and pass the URL (or URI) in as the data to view. So this time we need to initialise the intent with two pieces of information: an action and some data to act upon.
Since the code we need is not very different from what we wrote above, we'll pull that code apart into more reusable routines, along with a variation that solves this new requirement:
So now we have 3 overloaded versions of a new LaunchActivity
function.
One takes an action string, one takes an action string and a URI, and one takes
an intent object (and is called by both the other overloads once they have built
up an intent object).
The battery usage routine calls the overload that just takes an action string.
The new LaunchURL
routine takes the Delphi URL string and translates
it into a Java string before passing this off to the parse
class method of the Android URI
class, in order to get a URI
object returned. An intent object is then created this time with two constructor parameters: a view action and the URI.
The ACTION_VIEW
data display action is designed to launch
the sensible viewing action for many data types. There's bound to be one suitable
or displaying URL, either the system browser, or maybe an additional browser
you have installed, so the code doesn't bother checking the return value from
LaunchActivity
. Indeed if there is more than one available choice and
the user has not previously specified one option to always use then Android will
display a chooser dialog showing all the options.
It's useful to note that Android understands a variety of URI formats and will display them in appropriate applications. For example:
You can see above how easy it is to prepare a phone call for the user. You need
no special permissions to ask the system dialler to show up with a number plugged
in. If you wanted to actually make the call, then you'd need to use the telephony
APIs and require the appropriate CALL_PHONE
permission to use it, which is perhaps
not as desirable.
It's a similar story with SMSs. There are APIs that allow you to craft an SMS message
and then send it, and optionally identify if it was received and so forth. However
use of these APIs requires the SEND_SMS
permission, and it is advisable to keep
permission requirements to a minimum to avoid arousing suspicion in the user who
may bypass/reject your app because of them.
That said, if you are happy to require the permission (set in your Android manifest file as documented by Google here but achieved in Delphi through the project options dialog as documented by Embarcadero here) then that is just fine. Here's a unit that does what is required:
Now, back to the point about avoiding permissions. If you want to keep your permission requirements right down then the smart thinking involves launching the system SMS activity and having the user press the Send button. Launching activities requires no extra permissions primarily because the activity already exists on the device. In order to be there it is part of the system or the user has already accepted the permission requirements when installing it.
To launch the SMS app we have another routine, CreateSMS
, to add to
the LaunchActivities unit:
A couple of things to note here:
sms_body
into the intent to represent the content of the
SMS messageWith SMSs above we saw the intent starting to be loaded up with additional parameters required by the target activity. For launching an email we set even more parameters. Here's another routine for the LaunchActivities unit:
This sends an email to a single recipient with a specified subject and content, adding these data items in as extra string data. You can see that you could choose to send a plain text email but an RFC 822 format email is specified to try to ensure that only email apps will pick up the intent.
As well as EXTRA_TEXT
you can also send HTML format content
by also specifying EXTRA_HTML_TEXT
.
The code doesn't wait to see if the system will pop up a chooser dialog in the case that there are multiple apps that can handle the intent. Instead it directly invokes the chooser dialog for our intent using a custom prompt string.
If you want to support multiple email recipients you have to get a bit clever with setting up Java arrays in Delphi. In this case you need to put the recipients in a Java string array. The following additional overload has some code to show you how this looks:
As well as EXTRA_EMAIL
for the recipient(s) you can also use
EXTRA_CC
and EXTRA_BCC
.
If you want to take it a step further and send an attachment this is also possible
but I'm guessing it's a little less common. There's a post on StackOverflow that shows some code to do this
by getting a URI for the file location and adding that URI into an Android Parcelable
object and then adding that to the
intent as an EXTRA_STREAM
item (though other Android code I've
seen puts the URI directly into the intent, as per the documentation).
Something that crops up regularly in discussions around mobile app building is how to incorporate barcode scanning behaviour.
Many Android users have installed the open source ZXing (Zebra Crossing) barcode scanner app, which is designed to make a scan activity available. It is quite straightforward to invoke the activity if you've checked the ZXing "documentation" and therefore know the action string and optionally some data strings:
However we now have something of a problem on our hands. Having launched the activity, how do we get information back from it to identify what barcode, if any, was scanned?
Before looking at the standard Android approach to this problem, let's have a small digression to a quite popular solution that ZXing makes possible. ZXing scanner puts the scanned barcode data on the clipboard when scanned, so you simply need to check the device's clipboard and if a new value appears there then that's likely to be what was scanned. This approach was documented for Delphi XE5 by John Whitham and for documented for Delphi XE6 by Steffen Nyeland.
And now back to the standard Android approach.... Earlier code snippets that launch
an activity do so using the current activity's startActivity()
method, which launches an activity
and forgets all about it. More interesting for our current purpose is startActivityForResult()
, which is an activity method
that launches an activity with a specified request code and then waits for the activity
to exit and return a result code. When the launched activity does exit your activity's
onActivityResult()
method is called with your request
code and the activity's result code.
So, in order to launch an activity and pick up its result we need to implement
onActivityResult()
for Delphi's underlying activity. The rest of this
article looks at how we achieve such a goal but we'll leave this section with the
modified activity launching code:
Delphi apps on Android are native code libraries starting at a few megabytes in size in size, packaged into an .apk file along with some compiled Java, a few resources and an Android manifest. The native ARMv7 code can communicate with the Java world using the Java Bridge (or JNI Bridge as it is sometimes called), but this relies on Delphi representations of the Java classes being present.
This is quite like Delphi for Win32 talking to Windows APIs - many APIs are pre-declared for you and some you need to create the declarations for yourself if they aren't catered for by the Delphi RTL. Similarly Delphi XE5 has many Android classes represented, but also many more that are not, so there is good scope for being required to get on top of Delphi's Java Bridge to call into the Android API.
FireMonkey provides much functionality necessary for business applications needing to access data and display it to the user, but some common aspects of the Android OS are not 'wrapped up' in FireMonkey classes.
At this point you can call upon the various Android APIs that have been provided, or craft new Java Bridge APIs if need be (as we did for the toast API earlier).
In cases where this isn't simply a case of calling into more APIs whose declarations need to declared with Java Bridge interfaces this poses a problem. Some examples of areas of the Android OS that are not wrapped up in XE5 and are difficult to incorporate are:
In Delphi XE5 these things and more require additional Java code to implement. Then some light hacking is needed to make use of this Java code in a Delphi package, which tends to get in the IDE's way, so IDE building/installing of the application becomes interesting, and debugging becomes next to impossible. These consequences tend to force you into managing a pair of synchronised projects:
The specifics of the approach to handle a launched activity result will require some of the aforementioned light hacking. This may seem rather onerous and cumbersome but it is what it is and, in Delphi XE5, it is necessary.
The steps required for responding to a launched activity's result are as follows:
onActivityResult()
method that calls into
a native method, which we'll implement in Delphidx.bat
DexMerger
classThat's quite a daunting list but fear not. Fortunately many of the Java-oriented steps can be wrapped up by a suitably crafted batch (.bat) file or command script (.cmd) file. And I'm sure if you really wanted to, you could also create a PowerShell script to do the same, but I've stuck with the old scripting that I know how to work with.
The standard activity in any Delphi application is a Java class called FMXNativeActivity
in the com.embarcadero.firemonkey
package (or namespace), which actually
inherits from the Android NativeActivity
class. We need to inherit from FMXNativeActivity
,
override onActivityResult()
and have it call a native method with an
equivalent signature. Here's the code I rustled up for the job, which can be found
in the java\src\com\blong\test
directory under the project directory
in a file called NativeActivitySubclass.java
.
You might notice the Java code has a bunch of trace statements in (calls to Log.d()
). You can also emit the same type of trace
calls from your Delphi code by ensuring FMX.Types is in your uses
clause and calling
Log.d
there. These trace statements can be viewed in the
Android Device Monitor or its deprecated predecessor Dalvik Debug Monitor Server (DDMS).
Also, tucked away in the code is a dialog display helper message, which we'll come back to look at later on.
In the java
project subdirectory is a batch file called build.bat
.
Before invoking the batch file you must ensure you have done the required preparatory
work.
Prep step #1: The first preparatory job is to add a few directories from the Android SDK and the JDK to your System path.
You can do this either:
PATH
environment variable using steps outlined here or here
SET
commandsSET
commands
Note that the required directories will vary depending on whether you already had
an Android SDK installed, or whether you let Delphi install the Android SDK and,
if so, where you told it to install it. For example you may have already installed
the Android SDK in C:\Android\android-sdk-windows
, or Delphi may have
installed it into C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\adt-bundle-windows-x86-20130522\sdk\android-4.2.2
or you may have given Delphi a specific target directory, meaning the Android SDK
path is, say, C:\PlatformSDKs\adt-bundle-windows-x86-20130522\sdk\android-4.2.2
.
Directories to add to the path are:
build-tools
directory,
which might be C:\Android\android-sdk-windows\build-tools\18.0.1
or
C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\adt-bundle-windows-x86-20130522\sdk\android-4.2.2
or somewhere else altogether.bin
directory, which will be something along the lines of
C:\Program Files (x86)\Java\jdk1.6.0_23\bin
or C:\Program Files\Java\jdk1.7.0_25\bin
.
I'd certainly recommend extending the Windows search path permanently through the
environment variables option in the system properties dialog so you can run Android
SDK commands and Java commands at any point going forwards, but if you wanted to
run SET
commands at the command prompt or from within the batch file
they might look a little like this:
Once the system path has been updated you will be able to successfully launch the following executables from a command prompt. If you updated the global path then it will be any command prompt launched after you've made the change. If you changed a local environment by running commands at a command prompt then you can run the commands from that command prompt. The commands are:
If you cannot launch any of these commands then go back and review your path settings as they are likely wrong.
Prep step #2: The next preparatory job is to open the batch file in an editor and make appropriate edits to ensure the various environment variables set at the start are correct.
This is what the batch file looks like:
You should modify the set
commands for the following environment variables
and ensure they refer to appropriate existing directories:
ANDROID
- this should point at the Android SDK installation directory,
e.g. C:\Android\android-sdk-windows
or C:\Users\Public\Documents\RAD
Studio\12.0\PlatformSDKs\adt-bundle-windows-x86-20130522\sdk
ANDROID_PLATFORM
- this needs to be set to one of the installed Android
SDK platform directories, found in the Android SDK's platform
directory.
The Delphi installed SDK installs the android-17
platform directory.DX_LIB
- This needs to be set to the directory containing the
dx.jar
Dalvik support library. This is found in the lib
directory
2 levels under the Android SDK's build-tools
directory. You will need
to look to find the correct intermediate directory for your SDK installation. The
batch file suggests this may either be something like android-4.2.2
or something like 18.0.1
.EMBO_DEX
- should point at a classes.dex
file shipped
with Delphi. My installation has one stored as C:\Program Files (x86)\Embarcadero\RAD
Studio\12.0\lib\android\debug\classes.dex
but the environment variable
value is surrounded in quotes due to the spaces in the path.Prep step #3: The final preparatory job before running the batch file is to generate a compiled Java version of Embarcadero's classes.dex file, which will be a file called classes.jar.
The current classes.dex file, found in C:\Program Files (x86)\Embarcadero\RAD
Studio\12.0\lib\android\debug
and also in C:\Program Files (x86)\Embarcadero\RAD
Studio\12.0\lib\android\release
was compiled Java code, but has been
run through the Android SDK dx
tool to generate DEX (Dalvik Executable)
code. We need to undo that step to give us a file we can use to help our descendant
activity class compile ok.
To turn a .dex file back into a .jar file you can use the open source Dex2Jar tool. Once you've installed Dex2Jar you can use a command like this to process the file:
d2j-dex2jar.bat -o classes.jar "C:\Program Files (x86)\Embarcadero\RAD Studio\12.0\lib\android\debug\classes.dex"
Be sure to put the new classes.jar file in your project's java
subdirectory.
Beware that if you move to a different version of Delphi, which includes applying update packs to your current version of Delphi, then you will need to re-do this in case the code in classes.dex was updated and/or changed.
Now, at long last, we can run the Java build script...
To run the build script requires you to first launch a RAD Studio Command Prompt - you can do this in Windows 7 or Windows 8.x by going to the Start menu or Start screen and typing:
RAD Studio
and then selecting the RAD Studio Command Prompt item that is found. Or you can just hunt through the program groups to find it.
In the RAD Studio Command Prompt you should change directory to the one containing
the build.bat
batch file and then invoke it by running: build
This should proceed to:
dx.bat
Android SDK scriptDexMerger
class in the dx.jar
libraryIf any of these steps fails then you should review the environment variables and see which one has not been set correctly.
In the case that the dx
command fails with the error:
bad class file magic (cafebabe) or version (0033.0000)
then this has a specific cause. You have JDK 1.7 installed (this is what Delphi
will install for you) but dx
expects Java code as compiled by JDK 1.6.
To resolve this issue, you can either switch back to JDK 1.6 or modify the javac.exe command-line to have some additional command-line switches, which force the JDK 1.7 compiler to emit JDK 1.6 compatible Java byte code.
Edit the build script batch file to include this in the javac
command-line:
-source 1.6 -target 1.6
Now try and rebuild and the error should not recur.
After a successful build you will have a new classes.dex
file in the
project's java\output\dex
directory, which contains the native activity
descendant class that will respond to launched activity results in addition to all
the regular FMX Java code and Android support library included there by default.
At last it is time to open the sample project in the Delphi XE5 IDE. The main UI-based functionality in the application is quite trivial - it is a bunch of buttons that call the activity launching functionality looked at thus far in this article, as well as a final button that launches the barcode scanner to scan a barcode.
We'll come back to this last button shortly. First, however, we have a little more setup to do in the IDE, so choose the Project, Deployment menu item, which gives us this:
You should note that the normal classes.dex
file from the RAD Studio
directory has been de-selected. Instead, the newly created classes.dex
in the java\output\dex
directory has been selected to be deployed to
the same place: the Android package's classes
directory. This gets
our native activity descendant class on board within the Android package file.
You should be aware that the Deployment Manager in Delphi XE5 has a problem with
substituting alternate versions of classes.dex
into Android application
packages. The problem is logged in Quality Central as QC 118472.
The issue manifests itself by Delphi automatically re-selecting the default shipped
classes.dex
when you switch configurations in the Deployment Manager.
This overrides the replacement version and so things very much stop going according
to plan, so keep an eye open for this issue occurring.
The Java code calls down to a native onActivityResultNative
method,
which is implemented and registered in the code you see below. As you glance up
and down the code you might observe that it's a bit hairy, but let's look through
it and see if we can get a handle on it.
The actual native Delphi routine is in fact implemented as the OnActivityResultNative
function. Like all JNI functions it uses the C calling convention and has the same
first couple of arguments. The first argument is the JNI interface pointer,
through which we manipulate Java objects. The second argument points to the Java
object for an instance method or to the Java class for a class method, rather like
Self
does in Delphi methods.
After these initial arguments we have JNI representations of the arguments taken
by the Java onActivityResult()
method, which in this case include two
primitive types - integers - and a reference type - an Intent
object reference.
The intent argument becomes a JNIObject
, which is Delphi's version
of the JNI specification's jobject
type, used to represent reference
types.
When this native method gets called we queue up some code to run in the FMX thread (remember that
the JNI native routine is invoked in the Java UI thread). The code invokes a method
on the main form passing the received parameters, wrapping the JNI intent parameter
in a JIntent
Java bridge (aka JNI bridge) interface reference to give
the form method access to the Java intent object. The called method has been added
to look like a regular method, maybe even look a bit like an event handler, to handle
the activity result callback.
Ahead of any callbacks actually occurring this JNI routine must first be registered
as the implementation of the native method onActivityResult()
, which
can be called from Java. This task is performed by RegisterDelphiNativeMethods
,
which gets called in the form's OnCreate
event handler.
RegisterDelphiNativeMethods
uses an RTL helper to get a JNI interface
pointer (the same sort of thing that is given to a JNI method as the first parameter)
that will allow the FireMonkey activity object to be manipulated. Then it fills
up a JNINativeMethod
record with information that JNI can use to map
the Java method name and signature to our Delphi routine. Getting the right signature
in the relevant format requires a little bit of know-how.
The trick here is to use one of the JDK tools to dump information about NativeActivitySubclass
and this will give us the appropriately encoded signature information. The tool
in question is javap.exe: the Java class file disassembler.
As it is currently set up the build script deletes all the intermediate files, including the compiled activity descendant. So to work this step through (if you really want to run it - bear in mind I've already done this and used the information gleaned therein within the code) you'd need to re-run the build script after commenting out (or deleting) the tidying up section, particularly removing this line:
Now inside of your project's java\output\classes\com\blong\test
directory
you will have a few .class files. From your project's java
directory
you can run this command:
javap -s -classpath output\classes com.blong.test.NativeActivitySubclass
and this will emit information about all the method signatures of the specified class. I've highlighted the key pieces of information that have been used to set up the JNI registration record.
RegisterDelphiNativeMethods
then proceeds to use JNI routines to get a local reference to the FireMonkey activity object's class,
which it uses to register the native method against (the native method is
part of our app's activity class) before cleaning up the local reference.
There is quite a bit to think about when setting this up in a new application but hopefully with the various steps explained it at least becomes understandable.
After that tortuous journey we can now actually write a handler for the activity result!
The application can now be compiled to a native Android library and then deployed into an Android application package (.apk file) using the Project, Compile ActivitiesXE5 and Project, Deploy libActivitiesXE5.so menu items respectively.
If you wish you can skip those two steps and have them done implicitly by choosing
the Run, Run Without Debugging menu item (or by pressing Ctrl+Shift+F9
).
This will uninstall the app if already installed, and then install the new version
onto the target device.
Note that despite choosing the Run Without Debugging menu item, the application
will not be launched on the target device. The same is true if you choose Run, Run
(F9
). This is due to another shortcoming of the IDE in which it assumes
the launch activity will be the stock FireMonkey activity and so is unable to contend
with the situation we have here where we are using a descendant of the stock activity,
which has a different class name. This issue is open in Quality Central as QC 118450.
Clearly with the issues with launching the application there is no possibility of debugging your code. You cannot attach to a running Android process as you can with a Windows process, for example.
If you need to actively debug (as opposed to some agricultural equivalent such as
using calls to Log.d
from the FMX.Types unit) then it is incumbent upon you to set
up a pair of almost-mirror projects. One project will have all these hacks described
thus far on this page, and will enable launched activity results to be received
by your app. The other project will use the same application logic and units, but
will ignore all aspects of setting up activity result response. Consequently this
second project should support regular Android app debugging.
Over and above debugging your applications there are some issues with the code you put in an application that is invoked from an Android callback, as we have done here. You have seen that we can be successfully notified of an activity finishing and returning a result code, and we've emitted a toast message and all was well.
Or so it seems.
Actually I deliberately chose to use a toast rather than a message box (as in
ShowMessage
, MessageDlg
et al) for the simple reason that
I didn't want my demo app to hang.
The thing is that for some odd reason, which has been consistent through the
release of both Delphi XE5 and Delphi XE6, if FMX code is called from some Android
callback routine and that FMX code invokes code that has a nested message loop (as
in the sort of thing that ShowMessage
and MessageDlg
do)
then you won't see the message box. Instead the app will turn into a black screen.
Eventually after some delay the OS will step in with an ANR message (Application
is Not Responding), offering to close the offending app.
This is not good.
Actually I deliberately chose to use a toast rather than a message box (as in
ShowMessage
, MessageDlg
et al) for the simple reason that
doing so adds in something else to remember.
Specifically, what you must remember is that if FMX UI code is called from some
Android callback routine (such as this onNewIntent
method) you must
remember to get the code to run in the FMX UI thread, not the Android/Java UI thread,
otherwise the app will disappointingly hang.
It's quite straightforward, you can just run the code through TThread.Queue
and it will get synchronised to run in the FMX UI thread, rather like this:
You can easily extend the functionality of your application by taking advantage of pre-installed system activities, and you can also make use of custom activities if you check whether they are available. This is a convenient model and goes some way to bring us the component-building model of bolting together large lumps of code into an application that was promised in the 90s.
However when it comes to receiving feedback from launched activities in a Delphi Android application things quickly start to get a bit sticky. The onerous Java-based steps for dealing with the problem are not outlandish, but they are definitely tedious and cumbersome. It is a fact that Delphi XE6 makes the business of hooking into launched activity result codes considerably simpler than this and we'll look at that in an update to this article. However even in Delphi XE5 we can achieve communication from launched activities if it is required.
There are some wrinkles in how this all comes together, but Delphi XE5 is Embarcadero's first stab at Android development support, so hopefully some of these wrinkles will smooth out over time and things will continue to improve in future product updates and releases.
Go back to the top of this page