XMPP client with Android

The use case

I needed to send asynchronously messages to my Android app, first choice that popped up to my mind is using JMS But since Android (I should say Dalvik) does not include all javax packages (especially javax.naming.*) I gave up using JMS under Android.

Instead, I wrote a simple MessageDrivenBean (using Java EE6 annotation @MessageDriven(mappedName=”jms/myFancyTopic”) Server side, that will consume my JMS messages and then write them back (as xmpp messages) to an XMPP Server (pubsub). The Android application will then subscribe and listen for incoming XMPP messages to retrieve the content.

So fare so good

I was looking for a good XMPP library for Android, after some search I came to the conclusion that there are two ways to achieve my design
– Do It Yourself ! code an XMPP client from scratch, conform to Dalvik limitations
– Use asmack
Since I’m a lazy programmer I choose the latter 🙂

Asmack

Asmack is the Android build of the famous smack Api (Open Source XMPP formerly Jabber client library for instant messaging and presence).

There are some known Bugs though

 1) Use IPv4

Otherwise you’ll have an Exception java.net.SocketException: Bad address family

System.setProperty("java.net.preferIPv6Addresses", "false");

2) Thou shalt use BKS !

The second problem I encountered is related to location and the type of the truststore under Android.
Unlike a regular Java Virtual Machine where the truststore is located under

/lib/security/cacerts

In Android the cacerts is here

/system/etc/security/

(I don’t known if this Path is normalized or is subject to modification – I tested successfully with Froyo 2.2)

So you have to set these properties also

config.setTruststorePath("/system/etc/security/cacerts.bks");
config.setTruststorePassword("changeit");
config.setTruststoreType("bks");

Another details, Android use a different format to store the certificates, again unlike the regular JVM where the Keystore and Truststore container are JKS (JavaKeystore) within Android we use BKS (BouncycastleKeyStore)

3) work around ClassCastException: org.jivesoftware.smack.util.PacketParserUtils

The smack jar include smack.providers file under META-INF, this file allow the XMPP ProviderManager to be configured when initialized. But since Dalvik does not allow the loading of META-INF files from the filesystem, we have to register every provider manually.
I used the code provided by Florian Schmaus from the gtalksms project

ConfigureProviderManager.configureProviderManager();

Note: I attached my modified version of ConfigureProviderManager here.

Putting it all together

Here is the Android XMPP client


ConfigureProviderManager.configureProviderManager();
			System.setProperty("java.net.preferIPv6Addresses", "false");

			ConnectionConfiguration config = new ConnectionConfiguration("192.168.0.1");
			config.setDebuggerEnabled(true);// Enable xmpp debugging at Logcat

			// set up cert location
			config.setTruststorePath("/system/etc/security/cacerts.bks");
			config.setTruststorePassword("changeit");// this is the default password
			config.setTruststoreType("bks");

			mConnection = new XMPPConnection(config);
			mConnection.connect();
			// Log into the server as jack, passwd reacher
			mConnection.login("jack", "reacher");

			// Create a pubsub manager using an existing Connection
			String pubSubAddress = "pubsub." + mConnection.getServiceName();
			PubSubManager mgr = new PubSubManager(mConnection, pubSubAddress);

			// Get the node
			LeafNode node = (LeafNode) mgr.getNode("MY_NODE_NAME");
			node.subscribe("jack@192.168.0.1");

			// This will collect all XMPP messages
			PacketCollector collector = mConnection.createPacketCollector(new PacketFilter() {
				public boolean accept(Packet packet) {
					return true;
				}
			});

			while (true) {
				Packet packet = collector.nextResult();

				if (packet instanceof Message) {

					Collection pktExt = ((Message) packet).getExtensions();

					for (PacketExtension ext : pktExt) {
						if (ext instanceof EventElement) {
							String json = ((PayloadItem) ((ItemsExtension) ((EventElement) ext)
									.getExtensions().get(0)).getExtensions()
									.get(0)).toXML();

							System.out.println(">>>>>>GOT JSON?<<<<<<<<: " + json);

						}
					}
				}
			}

Bonus Track!

This is the server code (Intercepting JMS message – transforming the message to JSON and finally sending the Json message to XMPP pubsub)


    		//=======================================
    		//==========    XMPP CONNECTION    ======
    		//=======================================

    		  ConnectionConfiguration config = new ConnectionConfiguration("127.0.0.1");
			  XMPPConnection connection = new XMPPConnection(config);
			  connection.connect();
			  connection.login("Test", "Test");// Log into the server

		      PubSubManager mgr = new PubSubManager(connection);

		      // Create the node
		      ConfigureForm form = new ConfigureForm(FormType.submit);
		      form.setAccessModel(AccessModel.open);
		      form.setDeliverPayloads(true);
		      form.setNotifyRetract(true);
		      form.setPersistentItems(true);
		      form.setPublishModel(PublishModel.open);

		      LeafNode myNode = null;
		        try{
		          mgr.getNode("MY_NODE_NAME");
		          //node exists, so delete
		          mgr.deleteNode("MY_NODE_NAME");

		        }catch(XMPPException e){//node does not exists,
		        }

				MyConstant.XMPP_PUB_SUB_LEAF = (LeafNode) mgr.createNode("MY_NODE_NAME", form);

			//=====================
    		//========  EJB  ======
    		//=====================

			@MessageDriven(mappedName = "jms/producer/Topic")
			public class XmppEjb implements MessageListener {

			@Override
			public void onMessage (Message message) {

				try {
					TextMessage msgTxt = (TextMessage) message;

					if (null != MyConstant.XMPP_PUB_SUB_LEAF ) {
						MyConstant.XMPP_PUB_SUB_LEAF.send(new PayloadItem(null,
								new SimplePayload("book", "pubsub:test:book", ""+msgTxt.getText()+"")));
					}

				} catch (JMSException e) {
					e.printStackTrace();

				} catch (XMPPException e) {
					e.printStackTrace();
				}

			}
		}

URLs

– My XMPP Server is Openfire

http://www.igniterealtime.org/projects/openfire/index.jsp

– Smack API

http://www.igniterealtime.org/projects/smack/index.jsp

http://www.igniterealtime.org/builds/smack/docs/latest/documentation/

– Asmack

http://code.google.com/p/asmack/

Advertisements

About nhachicha

Android Developer, share experience about Java, System and Android
This entry was posted in Android and tagged , , , , , , , . Bookmark the permalink.

5 Responses to XMPP client with Android

  1. flowdalic says:

    The aSmack project on googlecode didn’t get updates over the last year. It doesn’t seemed to be maintained any more. I recommend using my fork of asmack that also includes smack 3.2.2 and is in sync with upstream: https://github.com/Flowdalic/asmack
    Most, if not all, of the problems you encountered should be fixed there. And feedback is highly welcomed. What is the diff of your ProviderManager config against the one GTalkSMS uses?

  2. gilg says:

    Thank You Nabil for this great blog post.
    I am looking for a similar solution that supports bi-directional XMPP-JMS communication. Do have experience with such a solution?
    Do you think this could be relevant?
    http://activemq.apache.org/xmpp.html

  3. Your advice is amazingly fascinating.

  4. Aman Yadav says:

    your blog solved my sticky problem THANKSSSSS!!!!

  5. aligamba says:

    Thanks,,,your explanation is great…solved my problem

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s