Uso de XMPP en Android

Por (circunstancias) estoy trabajando con XMPP para un cliente multiplataforma (móvil) de mensajería, por su potencia, por ser un estándar conocido, y por las diversas implementaciones de gran calidad y extensibilidad que existen a nivel de servidor.

Para elegir una librería en Java, para usar en Android, la cosa está bastante clara: hay que tirar con Smack. Pero presenta una serie de problemas la librería, al menos con las versiones a día de hoy, que hacen que tengamos que buscar por otro lado: errores en la conexión por no poder utilizar el método SASL por defecto (al no estar presentes en la VM de Android algunos métodos utilizados), incapacidad de usar compresión, entre otras.

Entonces se llega a asmack, o sea, Smack para Android, que es un fork donde se han aplicado una serie de parches en la autenticación, DNS, y más.

Y a priori, todo funciona bien.

System.setProperty("java.net.preferIPv6Addresses", "false");
ConnectionConfiguration cc = new ConnectionConfiguration("domain",5222);
cc.setSecurityMode(SecurityMode.disabled);
cc.setCompressionEnabled(false);
cc.setReconnectionAllowed(true);

conn = new XMPPConnection(cc);
Connection.DEBUG_ENABLED = true;

try {
conn.connect();
if (conn.isConnected())
Log.d(TAG,"Conectado");
else {
Log.d(TAG,"No conectado");
return;
}

conn.login(user+"@domain", pass, "resource");

if (conn.isAuthenticated()) {
Log.d(TAG,"Autenticado");
res = true;
}
else
Log.d(TAG,"No autenticado");
} catch (XMPPException ex) { ex.printStackTrace(); }

Pero tiene pegas. Hay algunas funcionalidades que no van del todo bien, y para solventarlas en su mayoría hay que tirar de versiones de asmack que han parcheado. Yo tuve la suerte de encontrar un fork de asmack hecho por Flowdalic que soluciona la mayoría de problemas, como por ejemplo la búsqueda de usuarios en el servidor.

Pero hay un tema muy insidioso que nadie ha solucionado en sus JAR. Resulta que existe, dentro de la carpeta META-INF de los mismos, un archivo que se llama smack.providers que debería cargarse, puesto que cuenta con los IQ providers de la mayoría de añadidos útiles de XMPP, pero por cuestiones de cómo se generan los JAR de Android, no está presente. Por lo tanto -y que sepáis que es lo que más me dio por culo a la hora de encontrar qué fallaba- hay que registrarlos manualmente.

Antes de definir el ConnectionConfiguration, debemos añadir esta línea de código:

// poner entre el System.setProperty y el ConnectionConfiguration
configure(ProviderManager.getInstance());
// ha de ir siempre antes del establecimiento de la conexión

Y después crear una función que se llame configure, quedando como la que sigue:


// reemplazo para smack.providers ya que éste no funciona en asmack
private void configure(ProviderManager pm) {
//  Private Data Storage
pm.addIQProvider("query","jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider());

//  Time
try {
pm.addIQProvider("query","jabber:iq:time", Class.forName("org.jivesoftware.smackx.packet.Time"));
} catch (ClassNotFoundException e) {
Log.w(TAG, "Can't load class for org.jivesoftware.smackx.packet.Time");
}

//  XHTML
pm.addExtensionProvider("html","http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider());

//  Roster Exchange
pm.addExtensionProvider("x","jabber:x:roster", new RosterExchangeProvider());
//  Message Events
pm.addExtensionProvider("x","jabber:x:event", new MessageEventProvider());
//  Chat State
pm.addExtensionProvider("active","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider());
pm.addExtensionProvider("composing","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider());
pm.addExtensionProvider("paused","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider());
pm.addExtensionProvider("inactive","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider());
pm.addExtensionProvider("gone","http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider());

//   FileTransfer
pm.addIQProvider("si","http://jabber.org/protocol/si", new StreamInitiationProvider());
pm.addIQProvider("query","http://jabber.org/protocol/bytestreams", new BytestreamsProvider());
pm.addIQProvider("open","http://jabber.org/protocol/ibb", new OpenIQProvider());
pm.addIQProvider("close","http://jabber.org/protocol/ibb", new CloseIQProvider());
pm.addExtensionProvider("data","http://jabber.org/protocol/ibb", new DataPacketProvider());

//  Group Chat Invitations
pm.addExtensionProvider("x","jabber:x:conference", new GroupChatInvitation.Provider());
//  Service Discovery # Items
pm.addIQProvider("query","http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
//  Service Discovery # Info
pm.addIQProvider("query","http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());
//  Data Forms
pm.addExtensionProvider("x","jabber:x:data", new DataFormProvider());
//  MUC User
pm.addExtensionProvider("x","http://jabber.org/protocol/muc#user", new MUCUserProvider());
//  MUC Admin
pm.addIQProvider("query","http://jabber.org/protocol/muc#admin", new MUCAdminProvider());
//  MUC Owner
pm.addIQProvider("query","http://jabber.org/protocol/muc#owner", new MUCOwnerProvider());
//  Delayed Delivery
pm.addExtensionProvider("x","jabber:x:delay", new DelayInformationProvider());
//  Version
try {
pm.addIQProvider("query","jabber:iq:version", Class.forName("org.jivesoftware.smackx.packet.Version"));
} catch (ClassNotFoundException e) {
Log.w(TAG, "Can't load class for org.jivesoftware.smackx.packet.Version");
}
//  VCard
pm.addIQProvider("vCard","vcard-temp", new VCardProvider());
//  Offline Message Requests
pm.addIQProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider());
//  Offline Message Indicator
pm.addExtensionProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider());
//  Last Activity
pm.addIQProvider("query","jabber:iq:last", new LastActivity.Provider());
//  User Search
pm.addIQProvider("query","jabber:iq:search", new UserSearch.Provider());
//  SharedGroupsInfo
pm.addIQProvider("sharedgroup","http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider());
//  JEP-33: Extended Stanza Addressing
pm.addExtensionProvider("addresses","http://jabber.org/protocol/address", new MultipleAddressesProvider());
}

Y con esto ya nos funcionarían todas las características, doy fe de ello: ya me funcionan IncomingFileTransfer y OutgoingFileTransfer, los chats multiusuario, e incluso los mensajes de OfflineMessageManager.

No sé si soy manco o qué, pero darme cuenta de todo esto y hallar las soluciones me ha costado bastantes semanas de dolor y agonía, y aquí lo comparto con vosotros, si es que a alguno alguna vez le toca trastear con XMPP en Android.

Sólo se que estoy temblando pensando en el momento que tenga que portar esta aplicación a iOS xDD