Using the Google MapView in Android with Oxygene for Java

Brian Long Consultancy & Training Services Ltd.
January 2012

Page selection: Previous  Next

Oxygene

Organise a custom self-signed certificate

To install any Android application it must be signed with a certificate related to a key in a keystore. If you were unaware that your Android applications had been signed that would be because the default behaviour of Oxygene for Java is to have the Android SDK tools automatically sign each application with the Android debug key.

The first time you use the Android SDK tool chain by building an application an Android debug key is created in a keystore in %USERPROFILE%\.android\debug.keystore. When apkbuilder is invoked as part of the Android build cycle it signs the app using the key in this debug keystore. It does this because the Signing page in the project options defaults to have the Sign app with default Android debug key option selected in the Signing options list.

The default signing options

If you instead selected No signing from the list of Signing options then apkbuilder would be passed a -u switch telling it not to sign the package with the debug keystore, leaving you with an unsigned application that could not be installed.

Why all this information about certificates in keystores? Well, some time very soon we will need to get some intimate details from the keystore used to sign our app, give them to Google and get another piece of information back (called a Map API key). That Map API key will need to be compiled into our app for the MapView control to operate correctly at runtime.

We could just use the Android debug keystore to generate this MAP API key, but some forward thinking might suggest this is not such a good idea.

If the goal is to build an app that you might at some point want to publish on the Android Market you should be aware that such an app must be signed with a custom key. The Android debug key is insufficient - an app signed with the Android debug key will not be allowed onto the Android Market.

So it makes sense to take the step of creating yourself a self-signed certificate/key/keystore up front for all your published apps*, so that when you get the Google Map API key based on it, that Map API key will always be usable in your app. Otherwise at some later point when you decide to switch from the default Android debug keystore to a custom keystore, you'll need to get a new Map API key based on the custom keystore and locate and replace all occurrences of the old Map API key with the new one.

*When you publish apps on the Android Market it's your key that identifies you as the publisher. If you use the same key for all your apps, then they will all be associated with the same developer - you. So when you create a custom key for a published app it's really important to keep hold of it so you can use it for future apps and updates to existing apps.

Ok, so assuming you agree that making a new keystore containing a key and self-signed certificate is a good idea, we'll look at how to do so. However if you want to use the Android debug key for the time being then that's also just fine. It saves you a step whilst testing with the MapView control. You simply need to reference the Android debug keystore instead of the custom one when generating a Map API key in a later step, which you can now skip to.

The Java utility keytool.exe is used to create and manage certificates and keys in keystores. You may have the Java 6 or Java 7 JDK installed - I have the Java 7 JDK installed in C:\Program Files\Java\jdk1.7.0_02 and I've added C:\Program Files\Java\jdk1.7.0_02\bin to my Windows PATH - you should also add your JDK bin directory to the PATH for convenience.

keytool appears pretty much identical between the two JDK versions but, just in case, the JDK 6 and JDK 7 documentation for keytool is available here and here respectively. We'll need to use the -genkeypair switch (or -genkey swtch in versions prior to JDK 6), which generates a public key and associated private key, wraps them in a self-signed X.509 v3 certificate stored as a single-element certificate chain, and stores the certificate chain and private key in a new keystore file. You can see the arguments we can pass when using -genkeypair by running this command:

C:\Oxygene\Android\com.blong.googleapi>keytool -genkeypair -help
keytool -genkeypair [OPTION]...

Generates a key pair

Options:

 -alias <alias>                  alias name of the entry to process
 -keyalg <keyalg>                key algorithm name
 -keysize <keysize>              key bit size
 -sigalg <sigalg>                signature algorithm name
 -destalias <destalias>          destination alias
 -dname <dname>                  distinguished name
 -startdate <startdate>          certificate validity start date/time
 -ext <value>                    X.509 extension
 -validity <valDays>             validity number of days
 -keypass <arg>                  key password
 -keystore <keystore>            keystore name
 -storepass <arg>                keystore password
 -storetype <storetype>          keystore type
 -providername <providername>    provider name
 -providerclass <providerclass>  provider class name
 -providerarg <arg>              provider argument
 -providerpath <pathlist>        provider classpath
 -v                              verbose output
 -protected                      password through protected mechanism

Use "keytool -help" for all available commands

We'll need to specify:

So a full call to keytool, including the -v switch for verbose output could look like this:

C:\Oxygene\Android\com.blong.googleapi>keytool -genkeypair -alias googleappkey -dname "CN=Acme Ltd.,O=Acme,C=UK" -storepass googleapp -keypass googleapp -keystore Properties\googleapp.keystore -validity 10000 -keysize 1024 -keyalg DSA -v 
Generating 1,024 bit DSA key pair and self-signed certificate (SHA1withDSA) with a validity of 10,00
0 days
        for: CN=Acme Ltd., O=Acme, C=UK
New certificate (self-signed):
[
[
  Version: V3
  Subject: CN=Acme Ltd., O=Acme, C=UK
  Signature Algorithm: SHA1withDSA, OID = 1.2.840.10040.4.3

  Key:  Sun DSA Public Key
    Parameters:DSA
        p:     fd7f5381 1d751229 52df4a9c 2eece4e7 f611b752 3cef4400 c31e3f80 b6512669
    455d4022 51fb593d 8d58fabf c5f5ba30 f6cb9b55 6cd7813b 801d346f f26660b7
    6b9950a5 a49f9fe8 047b1022 c24fbba9 d7feb7c6 1bf83b57 e7c6a8a6 150f04fb
    83f6d3c5 1ec30235 54135a16 9132f675 f3ae2b61 d72aeff2 2203199d d14801c7
        q:     9760508f 15230bcc b292b982 a2eb840b f0581cf5
        g:     f7e1a085 d69b3dde cbbcab5c 36b857b9 7994afbb fa3aea82 f9574c0b 3d078267
    5159578e bad4594f e6710710 8180b449 167123e8 4c281613 b7cf0932 8cc8a6e1
    3c167a8b 547c8d28 e0a3ae1e 2bb3a675 916ea37f 0bfa2135 62f1fb62 7a01243b
    cca4f1be a8519089 a883dfe1 5ae59f06 928b665e 807b5525 64014c3b fecf492a

  y:
    a358cf37 b42c5958 e45c6682 c26ed729 d7c3f431 be2343cb 7322a2aa b4b95e56
    bf28b179 8ecb064a 9d22aa8a 7309243b b8684eac 749055aa 6e8a9bc0 f3cbadae
    31349de6 6ac63a44 b6ee43a9 77516966 b9917e9d 26cf3064 8a379c24 add42a49
    1925085c 5c44de69 64d999ea 76e5a39c 8d9d92d2 b46f6358 32e791fe 6d09c063

  Validity: [From: Mon Jan 16 21:06:47 GMT 2012,
               To: Fri Jun 03 22:06:47 BST 2039]
  Issuer: CN=Acme Ltd., O=Acme, C=UK
  SerialNumber: [    4f6ea8e0]

Certificate Extensions: 1
[1]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 97 75 66 82 37 CB 0F 82   64 0F 72 A7 B2 BB 22 24  .uf.7...d.r..."$
0010: 4D 72 06 B6                                        Mr..
]
]

]
  Algorithm: [SHA1withDSA]
  Signature:
0000: 30 2C 02 14 71 52 DF 4D   6F FD A2 24 72 60 DB BC  0,..qR.Mo..$r`..
0010: B0 D4 4F 3F 62 81 A1 3E   02 14 37 97 E5 99 39 3D  ..O?b..>..7...9=
0020: 41 5A 67 78 BA 20 A3 F2   4E FE CA 66 77 15        AZgx. ..N..fw.

]
[Storing Properties\googleapp.keystore]

Note: having created a custom keystore, using your own comapny name etc., you'll have no need to do so again unless you want to develop apps that have a different published developer listed.

As you can see from the command-line, the keystore is emitted into the project's Properties directory. If you want, you can now add the keystore to the project so it shows up in Solution Explorer - right-click on the Properties folder in the Solution Explorer, choose Add, Existing Item..., locate googleapp.keystore in the Properties directory and press Add.

Set up your app to be signed

In order to ensure your app is now signed with this self-signed custom key you will need to go back to the Signing page of the project properties, select Sign app with custom key from the Signing options list and fill in the details as per the command-line that created the keystore. During the build process this alternate signing process is done by the Java jarsigner.exe tool (documented for JDK 6 and JDK 7 here and here respectively).

Note: to ensure this custom key is used to sign the app in both Debug mode and Release mode you should use the Configuration dropdown and choose All Configurations before saving the changes to the project options.

Signing with a custom key

You'll perhaps notice that there are additional options available on the Signing page over and above what was passed on the keytool command-line: Digest algorithm and Signature algorithm. Additionally, if you are using the RTM initial release of Oxygene for Java, or an old trial download, you may not even see these options available to you - that's because they were added in the Spring 2012 release, which should be available to you if you've purchased Oxygene for Java.

What's this all about then? Well, when a key is used to sign an Android package, jarsigner uses a digest algorithm when digesting the unsigned entries of the package. If unspecified, JDK 6's jarsigner will default to using a SHA1 digest algorithm, whereas JDK 7's jarsigner defaults to using SHA-256. It would appear from what I've seen so far that Android really only recognises an SHA1 digest algorithm so it is important to be able to specify this, in case JDK 7 is installed. Of course if you're using JDK 6 there's little worry about.

The signature algorithm identifies the signature algorithm used to create the key in the certificate. Usually the default value works fine in JDK 6 and in JDK 7 but advanced users may need to select a different value, for example if using specially created custom keys.

Note: you may observe above and below that there have been various changes between JDK 6 and JDK 7 that have the potential to upset the Android development process (and there are others that are outside this article's remit). Fortunately, as these have been identified the issues have been reported and Oxygene for Java has been updated to protect against the installed JDK version.

If you are using the RTM release of Oxygene for Java or the trial version and happen to have JDK 7 installed, then you should read the signing footnote that explains how to overcome the problems introduced by JDK 7.

Get a Maps API key from Google

At this point you could make use of the MapView control and successfully build and deploy an app to a device. However the MapView will not render a map until we do one more thing. The MapView control requires a Google Map API key that has been logged against your Google account in order to operate correctly so here we'll see what's involved in getting one.

The information we require is the MD5 fingerprint of the certificate in your keystore. We can use keytool again to get this information:

C:\Oxygene\Android\com.blong.googleapi>keytool -v -list -alias googleappkey -keystore "Properties\googleapp.keystore" -storepass googleapp
Alias name: googleappkey
Creation date: 16-Jan-2012
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Acme Ltd., O=Acme, C=UK
Issuer: CN=Acme Ltd., O=Acme, C=UK
Serial number: 4f6ea8e0
Valid from: Mon Jan 16 21:06:47 GMT 2012 until: Fri Jun 03 22:06:47 BST 2039
Certificate fingerprints:
         MD5:  96:6D:83:D9:57:EA:27:D8:A3:28:2F:D8:08:F4:1F:EB
         SHA1: 0F:08:FB:6D:86:59:60:8B:3C:F1:56:F2:50:F9:C0:F2:02:2A:D4:40
         SHA256: 11:3B:33:05:42:3F:A0:C1:59:28:28:FD:A7:F9:1D:C4:C4:1F:6E:37:1F:F3:5A:C7:99:59:AA:37:DE:0D:44:72
         Signature algorithm name: SHA1withDSA
         Version: 3

Extensions:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 97 75 66 82 37 CB 0F 82   64 0F 72 A7 B2 BB 22 24  .uf.7...d.r..."$
0010: 4D 72 06 B6                                        Mr..
]
]

Note: in the command above the -v switch is used to get verbose information displayed. Without this switch keytool will simply display a single fingerprint. However, whilst with the JDK 6 version of keytool defaults to displaying the MD5 fingerprint, which is what we want, JDK 7's jarsigner now defaults to showing the SHA1 fingerprint. To cover all bases, just use the -v switch and take the MD5 fingerprint from the list of certificate fingerprints shown.

Having got the MD5 fingerprint of your certificate you then need to visit the Android Maps API Key Signup page. There you can agree to Google's Maps API terms and conditions, plug in your MD5 fingerprint and be presented with your Map API key. For the key that I created with the command-line above I am given an Android Maps API key of:

0OUa1B6eBarw7xYPoFo8dYfMnSbS3gXuBOTpdOg

This value needs to be given to a MapView control, either in the layout file it is defined in with the android:apiKey attribute, or you can pass it to the MapView constructor if creating the MapView control in code.

Set up the MapView widget

Now at last.... at long, long, last we can use the MapView control!

Let's add a new activity to the template Android project to show the Google map. Right-click on the project node in the Solution Explorer and choose Add, New Item..., choose Activity from the list, call the source file GoogleMapActivity.pas and press Add.

Let's set up a simple map UI in the layout file res\layout\googlemapactivity.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.        android:layout_width="fill_parent"
  4.        android:layout_height="fill_parent">
  5.  
  6.   <com.google.android.maps.MapView
  7.      android:id="@+id/mapView"
  8.      android:layout_width="fill_parent"
  9.      android:layout_height="fill_parent"
  10.      android:enabled="true"
  11.      android:clickable="true"
  12.      android:apiKey="0OUa1B6eBarw7xYPoFo8dYfMnSbS3gXuBOTpdOg"
  13.    />
  14.  
  15. </LinearLayout>

Notice the use of our key-specific Map API key in the MapView's android:apiKey attribute.

On the code side, in GoogleMapActivity.pas, we should change our activity's ancestor class from the default Activity class to MapActivity, which means we also need to add com.google.android.maps to the uses clause. MapActivity adds in some lifecycle management support and deals with setup and teardown of MapView's required services.

Note: the documentation for the MapView, MapActivity, and their associated classes can be found on the Google APIs Add-On reference page.

MapActivity defines one abstract method we have to override: the protected isRouteDisplayed() method. If we are using the map view to display any kind of route then we should return true, otherwise we return false. This method is just used by Google to collate statistical information (for accounting purposes) on the usage of the map control.

Another method we are typically expected to override is isLocationDisplayed(), however since this is not an abstract method the choice is really ours. This method needs a Boolean returned indicating if we are displaying the user's current location as ascertained by any kind of sensor, such as a GPS device.

So the most basic MapView activity template looks like this:

  1. type
  2.   GoogleMapActivity = public class(MapActivity)
  3.   private
  4.     var map: MapView;
  5.   protected
  6.     method isLocationDisplayed: Boolean; override;
  7.     method isRouteDisplayed: Boolean; override;
  8.   public
  9.     method onCreate(savedInstanceState: Bundle); override;
  10.   end;
  11.  
  12. ...
  13.  
  14. method GoogleMapActivity.onCreate(savedInstanceState: Bundle);
  15. begin
  16.   inherited;
  17.   // Set our view from the "GoogleMapActivity" layout resource
  18.   ContentView := R.layout.googlemapactivity;
  19.   map := MapView(findViewById(R.id.mapView));
  20. end;
  21.  
  22. method GoogleMapActivity.isLocationDisplayed: Boolean;
  23. begin
  24.   exit false
  25. end;
  26.  
  27. method GoogleMapActivity.isRouteDisplayed: Boolean;
  28. begin
  29.   exit false
  30. end;

Before we can test the app and see the results of our work there are still a couple of things left to do. Not least of which is the small matter of ensuring the new map activity can be invoked - currently the template application's main activity just displays numbers when you click the button.

To make the main activity work with the map activity do the following:

startActivity(new Intent(self, typeOf(GoogleMapActivity)));

To tidy things up, in res\values\strings.xml, remove the definition of the string my_button_text_2, then update the app_name string and add a new string definition as follows:

<string name="app_name">Google Map App</string>
<string name="google_map_activity">Google Map Activity 1</string>

And finally we can use these strings in the Android manifest by changing the new automatically added GoogleMapActivity activity declaration line in Properties\AndroidManifest.xml from this:

<activity android:label="GoogleMapActivity" android:name="com.blong.googleapi.GoogleMapActivity"></activity>

to this:

<activity android:label="@string/google_map_activity" android:name="com.blong.googleapi.GoogleMapActivity" />

And now we're ready to build!


Go back to the top of this page

Go back to start of this article

Previous page

Next page