it.yup.xmpp.XMPPClient.java Source code

Java tutorial

Introduction

Here is the source code for it.yup.xmpp.XMPPClient.java

Source

/* Copyright (c) 2008 Bluendo S.r.L.
 * See about.html for details about license.
 *
 * $Id: XMPPClient.java 1597 2009-06-19 11:54:12Z luca $
*/

package it.yup.xmpp;

import it.yup.transport.BaseChannel;
import it.yup.transport.SocketChannel;

// #mdebug
//@
//@import it.yup.util.Logger;
//@
//#enddebug

import it.yup.util.RMSIndex;
import it.yup.util.Utils;
import it.yup.xml.Element;
import it.yup.xmlstream.AccountRegistration;
import it.yup.xmlstream.BasicXmlStream;
import it.yup.xmlstream.EventQuery;
import it.yup.xmlstream.EventQueryRegistration;
import it.yup.xmlstream.PacketListener;
import it.yup.xmlstream.SocketStream;
import it.yup.xmlstream.StreamEventListener;

import it.yup.xmpp.packets.DataForm;
import it.yup.xmpp.packets.Iq;
import it.yup.xmpp.packets.Message;
import it.yup.xmpp.packets.Presence;
import it.yup.xmpp.packets.Stanza;

import java.io.IOException;
import java.util.Enumeration;
import java.util.TimerTask;
import java.util.Vector;
import org.bouncycastle.util.encoders.Base64;

import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Image;

public class XMPPClient implements StreamEventListener {

    /*
     * Few methods used to communicate to the application of xmpp events 
     */
    public interface XmppListener {

        void removeContact(Contact c);

        void removeAllContacts();

        void updateContact(Contact c, int chStatus);

        void authenticated();

        void rosterXsubscription(Element e);

        void playSmartTone();

        void askSubscription(Contact u);

        void handleCommand(Contact c, String preferredResource);

        void connectionLost();

        void showAlert(AlertType type, String title, String text, Object next_screen);

        void handleTask(Task task, boolean display);

        Object handleDataForm(DataForm df, byte type, DataFormListener dfl, int cmds);

        void commandExecuted(Object screenToClose);

        void showCommand(Object screen);

        void rosterRetrieved();

    }

    private Config cfg = Config.getInstance();

    /*
     * The features published by Lampiro are ordered as specified here:
     * http://tools.ietf.org/html/rfc4790#section-9.3
     */
    private String[] features = new String[] { MIDP_PLATFORM, NS_CAPS, NS_COMMANDS, NS_IQ_DISCO_INFO, NS_IBB,
            NS_MUC, NS_ROSTERX, JABBER_X_DATA, JINGLE, JINGLE_FILE_TRANSFER, JINGLE_IBB_TRANSPORT };

    /** the client instance */
    private static XMPPClient xmppInstance;

    // /** the authID value obtained during stream initialization */
    // public String _authID;

    public Roster roster;

    /** myself */
    private Contact me;

    /** my jabber id */
    public String my_jid;

    /** the used XmlStream */
    private BasicXmlStream xmlStream = null;

    /** The actual connection with the Server */
    private BaseChannel connection = null;

    /** true when the stream is valid */
    private boolean valid_stream = false;

    private EventQueryRegistration lostConnReg = null;

    // /** send the subscribe at most once per session */
    // private boolean lampiro_subscribe_sent = false;

    protected Image presence_icons[];
    protected Image presence_phone_icons[];

    /*
     * This task is used to retrieve asynchronously the roster
     * after going online. 
     */
    private TimerTask rosterRetrieveTask = null;

    /*
     * The time after rosterRetrieveTask is scheduled after receiving
     * a presence
     */
    private int rosterRetrieveTime = 5000;

    /*
     * The time at which the last presence has been received
     */
    private long lastPresenceTime;

    /*
     * The number of sent bytes over the socket
     */
    //public static int bytes_sent = 0;
    /*
     * The number of received bytes over the socket
     */
    //public static int bytes_received = 0;
    /*
     * A flag used to enable or disable compression
     */
    public boolean addCompression = false;

    /*
     * A flag used to enable or disable TLS
     */
    public boolean addTLS = false;

    /*
     * the gateways whose contacts must be autoaccepted
     * i.e. the gateways whose presence has been subscripted 
     * within the current session
     */
    public Vector autoAcceptGateways = new Vector();

    private XMPPClient.XmppListener xmppListener;

    /*
     * Used to notify the XmppClient that the user jid and/or password
     * are changed from last login
     */
    private boolean newCredentials = false;

    /* 
     * The registration initializer
     */
    private AccountRegistration accountRegistration;

    /**
     * Get the total amount of traffic on the GPRS connection
     * 
     * @return an array with two elements: in / out traffic
     */
    public static int[] getTraffic() {
        return new int[] { BaseChannel.bytes_received, BaseChannel.bytes_sent };
    }

    public static String NS_IQ_DISCO_INFO = "http://jabber.org/protocol/disco#info";
    public static String NS_IQ_DISCO_ITEMS = "http://jabber.org/protocol/disco#items";
    public static String NS_COMMANDS = "http://jabber.org/protocol/commands";
    public static String NS_CAPS = "http://jabber.org/protocol/caps";
    public static String NS_BLUENDO_CAPS = "http://bluendo.com/protocol/caps";
    public static String NS_BLUENDO_PUBLISH = "bluendo:http:publish:0";
    public static String NS_IBB = "http://jabber.org/protocol/ibb";
    public static String NS_MUC = "http://jabber.org/protocol/muc";
    public static String NS_ROSTERX = "http://jabber.org/protocol/rosterx";
    public static String NS_MUC_USER = "http://jabber.org/protocol/muc#user";
    public static String NS_MUC_OWNER = "http://jabber.org/protocol/muc#owner";
    public static String NS_VCARD_UPDATE = "vcard-temp:x:update";
    public static String NS_NICK = "http://jabber.org/protocol/nick";
    public static String MIDP_PLATFORM = "http://bluendo.com/midp#platform";
    public static String JABBER_X_DATA = "jabber:x:data";
    public static String JABBER_IQ_GATEWAY = "jabber:iq:gateway";
    public static String IQ_REGISTER = "jabber:iq:register";
    public static String JINGLE = "urn:xmpp:jingle:0";
    public static String JINGLE_FILE_TRANSFER = "urn:xmpp:jingle:apps:file-transfer:1";
    public static String FILE_TRANSFER = "http://jabber.org/protocol/si/profile/file-transfer";
    public static String JINGLE_IBB_TRANSPORT = "urn:xmpp:jingle:transports:ibb:0";
    public static String BLUENDO_PUBLISH = "bluendo:http:publish:0";
    public static String USERID = "USERID";
    public static String VCARD_TEMP = "vcard-temp";
    public static String VCARD = "vCard";
    public static String BINVAL = "BINVAL";
    public static String NICKNAME = "NICKNAME";
    public static String PHOTO = "PHOTO";
    public static String FN = "FN";
    public static String EMAIL = "EMAIL";
    public static String BLUENDO_XMLRPC = "bluendo:bxmlrpc:0";
    public static String BLUENDO_REGISTER = "bluendo:register:0";
    public static String NS_DELAY = "urn:xmpp:delay";
    public static String DELAY = "delay";
    public static String[][] errorCodes = new String[][] { { "400", "Bad request" }, { "401", "Not authorized" },
            { "403", "Forbidden" }, { "404", "Item not found" }, { "406", "Not acceptable" },
            { "407", "Registration required" }, { "500", "Internal server error" },
            { "501", "Feature not implemented" }, { "503", "Service unavailable" }, };

    public Image getPresenceIcon(Contact c, String preferredResource, int availability) {
        Presence p = null;
        if (c != null) {
            if (preferredResource != null)
                p = c.getPresence(preferredResource);
            else
                p = c.getPresence();
        }
        if (availability >= 0 && availability < this.presence_icons.length) {
            if (p == null || p.pType == Presence.PC) {
                return this.presence_icons[availability];
            } else if (p.pType == Presence.PHONE) {
                return this.presence_phone_icons[availability];
            }
        }
        return null; // maybe we could return an empty image
    }

    private XMPPClient() {
        roster = new Roster(this);

        // preload the presence icons
        String mapping[] = Contact.availability_mapping;
        presence_icons = new Image[mapping.length];
        presence_phone_icons = new Image[mapping.length];
        try {
            presence_icons[0] = Image.createImage("/icons/presence_" + mapping[1] + ".png");
            presence_phone_icons[0] = Image.createImage("/icons/presence_" + mapping[1] + "_phone.png");
            presence_icons[1] = Image.createImage("/icons/presence_" + mapping[1] + ".png");
            presence_phone_icons[1] = Image.createImage("/icons/presence_" + mapping[1] + "_phone.png");
            presence_icons[2] = Image.createImage("/icons/presence_" + mapping[2] + ".png");
            presence_phone_icons[2] = Image.createImage("/icons/presence_" + mapping[2] + "_phone.png");
            presence_icons[3] = Image.createImage("/icons/presence_" + mapping[3] + ".png");
            presence_phone_icons[3] = Image.createImage("/icons/presence_" + mapping[3] + "_phone.png");
            presence_icons[4] = Image.createImage("/icons/presence_" + mapping[3] + ".png");
            presence_phone_icons[4] = Image.createImage("/icons/presence_" + mapping[3] + "_phone.png");
            presence_icons[5] = Image.createImage("/icons/presence_" + mapping[5] + ".png");
            presence_phone_icons[5] = Image.createImage("/icons/presence_" + mapping[5] + "_phone.png");
        } catch (Exception e) {
            // should not happen
        }

        //      presence_icons = new Image[mapping.length];
        //      for (int i = 0; i < presence_icons.length; i++) {
        //         try {
        //            presence_icons[i] = Image.createImage("/icons/presence_"
        //                  + mapping[i] + ".png");
        //         } catch (IOException e) {
        //            presence_icons[i] = Image.createImage(16, 16);
        //         }
        //      }

    }

    /**
     * Get the XMPP client (a singleton)
     * 
     * @return the unique instance of the client
     * 
     */
    public static XMPPClient getInstance() {
        if (xmppInstance == null) {
            xmppInstance = new XMPPClient();
        }
        return xmppInstance;
    }

    public void startClient() {

    }

    /** close the connection XXX i don't like this name */
    public void stopClient() {
        // saveToStorage();
    }

    //   /**
    //    * Add a listener for XMPP packets
    //    * 
    //    * @param _q
    //    *            the xpath like query
    //    * @param _l
    //    *            the listener
    //    * @return The registration object to be used with
    //    *         {@link #unregisterListener(EventQueryRegistration)} for removing
    //    *         the listener
    //    */
    //   public EventQueryRegistration registerListener(EventQuery _q, Object _l) {
    //      return BasicXmlStream.addEventListener(_q, _l);
    //   }
    //
    //   /**
    //    * Add a one time listener for XMPP packets
    //    * 
    //    * @param _q
    //    *            the xpath like query
    //    * @param _l
    //    *            the listener
    //    * @return The registration object to be used with
    //    *         {@link #unregisterListener(EventQueryRegistration)} for removing
    //    *         the listener
    //    * 
    //    */
    //   public EventQueryRegistration registerOneTimeListener(EventQuery _q,
    //         Object _l) {
    //      return BasicXmlStream.addOnetimeEventListener(_q, _l);
    //   }
    //
    //   /**
    //    * Remove a registered listener
    //    * 
    //    * @param reg
    //    *            the registration obtained with
    //    *            {@link #registerListener(EventQuery, PacketListener)}
    //    * 
    //    */
    //   public void unregisterListener(EventQueryRegistration reg) {
    //      BasicXmlStream.removeEventListener(reg);
    //   }

    /**
     * Queue a packet into the send queue
     * 
     * @param pack
     *            the packet to be sent
     */
    public void sendPacket(Element pack) {
        xmlStream.send(pack, Config.TIMEOUT);
    }

    /**
     * Send an Iq packet and register the packet listener for the answer
     * 
     * @param iq
     * @param listener
     *            (may be null)
     */
    public void sendIQ(Iq iq, IQResultListener listener) {
        if (listener != null) {
            IqManager.getInstance().addRegistration(iq, listener);
        }
        sendPacket(iq);
    }

    public Contact getMyContact() {
        return me;
    }

    /**
     * Start the XML Stream using the current configuration
     * 
     * @param register
     *            set true if the stream must create the account
     * @param newCredentials 
     * @return
     */
    public BasicXmlStream createStream(boolean register, boolean newCredentials) {
        this.newCredentials = newCredentials;
        // this must be done to clean the Roster after a reconnect
        this.roster.purge();
        buildSocketConnection();

        // connection = new SimpleBTConnector(
        // Config.HTTP_GW_HOST,
        // Config.HTTP_GW_PATH,
        // xmlStream
        // );

        // XXX useful with messages? I don't think so
        // eq = new EventQuery(Message.MESSAGE, null, null);
        // eq.child = new EventQuery(Iq.QUERY, new String[] { "xmlns" },
        // new String[] { "http://jabber.org/protocol/disco#items" } );
        // xmlStream.addEventListener(eq, adch);

        if (register) {

            EventQuery qReg = new EventQuery(BasicXmlStream.STREAM_ACCOUNT_REGISTERED, null, null);
            /*
             * The registration used to be notified of the registration
             */
            BasicXmlStream.addEventListener(qReg, this);

            accountRegistration = new AccountRegistration();
            xmlStream.addInitializer(accountRegistration, 0);
        }

        return xmlStream;
    }

    /**
     * Build the low level connection based on plain sockets
     */
    private void buildSocketConnection() {
        xmlStream = new SocketStream();
        // #ifndef BT_PLAIN_SOCKET
        connection = new SocketChannel("socket://" + cfg.getProperty(Config.CONNECTING_SERVER), xmlStream);
        // #endif
        ((SocketChannel) connection).KEEP_ALIVE = Long.parseLong(cfg.getProperty(Config.KEEP_ALIVE));
    }

    public void openStream() {
        String resource = cfg.getProperty(Config.YUP_RESOURCE, "Lampiro");
        xmlStream.initialize(cfg.getProperty(Config.USER) + "@" + cfg.getProperty(Config.SERVER) + "/" + resource,
                cfg.getProperty(Config.PASSWORD));

        EventQuery qAuth = new EventQuery(BasicXmlStream.STREAM_INITIALIZED, null, null);
        /*
         * The registration used to be notified of the authentication
         */
        BasicXmlStream.addEventListener(qAuth, this);

        if (!connection.isOpen()) {
            connection.open();
        }
    }

    public void closeStream() {
        if (connection.isOpen()) {
            connection.close();
        }
    }

    public void gotStreamEvent(String event, Object source) {
        if (BasicXmlStream.STREAM_INITIALIZED.equals(event)) {

            // all these registration are made here 
            // Register the handler for incoming messages
            EventQuery eq = new EventQuery(Message.MESSAGE, null, null);
            eq.child = new EventQuery(Message.BODY, null, null);
            BasicXmlStream.addEventListener(eq, new MessageHandler());

            // Register the presence handler
            eq = new EventQuery(Presence.PRESENCE, null, null);
            BasicXmlStream.addEventListener(eq, new PresenceHandler());

            // Register the disco handler
            eq = new EventQuery(Iq.IQ, new String[] { "type" }, new String[] { "get" });
            eq.child = new EventQuery(Iq.QUERY, new String[] { "xmlns" }, new String[] { NS_IQ_DISCO_INFO });
            BasicXmlStream.addEventListener(eq, new DiscoHandler());

            // Register the handler for dataforms (both as <iq/> and <message/>)
            // payloads
            DataFormHandler dh = new DataFormHandler();
            eq = new EventQuery(Message.MESSAGE, null, null);
            eq.child = new EventQuery(DataForm.X, new String[] { "xmlns" }, new String[] { DataForm.NAMESPACE });
            BasicXmlStream.addEventListener(eq, dh);
            eq = new EventQuery(Iq.IQ, null, null);
            eq.child = new EventQuery(DataForm.X, new String[] { "xmlns" }, new String[] { DataForm.NAMESPACE });
            BasicXmlStream.addEventListener(eq, dh);

            /* register handler for ad hoc command announcements */
            PacketListener ashc_listener = new PacketListener() {
                public void packetReceived(Element e) {
                    handleClientCommands(e, false);
                }
            };

            // XXX here we *must* use client capabilities
            /* register handler for ad hoc command presence announce */
            eq = new EventQuery(Presence.PRESENCE, null, null);
            eq.child = new EventQuery(Iq.QUERY, new String[] { "xmlns" },
                    new String[] { "http://jabber.org/protocol/disco#items" });
            BasicXmlStream.addEventListener(eq, ashc_listener);

            IqManager.getInstance().streamInitialized();
            roster.streamInitialized();

            stream_authenticated();
        } else if (BasicXmlStream.STREAM_ACCOUNT_REGISTERED.equals(event)) {
            xmlStream.removeInitializer(accountRegistration);
        }
    }

    public void stream_authenticated() {
        // create the self contact and setup the initial presence
        my_jid = xmlStream.jid;

        me = new Contact(Contact.userhost(my_jid), null, null, null);
        Presence p = new Presence();
        p.setAttribute("from", my_jid);
        String show = cfg.getProperty(Config.LAST_PRESENCE_SHOW);
        if (show != null && !"online".equals(show)) {
            p.setShow(show);
        }
        String msg = cfg.getProperty(Config.LAST_STATUS_MESSAGE);
        String tempPriority = cfg.getProperty(Config.LAST_PRIORITY, "0");
        p.setPriority(Integer.parseInt(tempPriority));
        p.setStatus(msg);
        // set capabilities
        String uri = NS_CAPS;
        Element cap = p.addElement(uri, "c");
        cap.setAttribute("node", XMPPClient.NS_BLUENDO_CAPS);
        cap.setAttribute("hash", "sha-1");
        cap.setAttribute("ver", getCapVer());

        // XXX I don't like this, it could be better to send capabilities with a
        // different hash in the version
        //      Element x = p.addElement(JABBER_X_DATA, "x");
        //      x.setAttribute("type", Iq.T_RESULT);
        //      Element field = x.addElement(JABBER_X_DATA, "field");
        //      field.setAttribute("var", "FORM_TYPE");
        //      field.setAttribute("type", "hidden");
        //      field.addElement(JABBER_X_DATA, "value").addText(MIDP_PLATFORM);
        //
        //      field = x.addElement(JABBER_X_DATA, "field");
        //      field.setAttribute("var", "microedition.platform");
        //      field.addElement(JABBER_X_DATA, "value").addText(
        //            System.getProperty("microedition.platform"));

        me.updatePresence(p);

        // we are connected, set the stream as valid
        valid_stream = true;

        // Listen for lost connections
        lostConnReg = BasicXmlStream.addEventListener(new EventQuery(BasicXmlStream.STREAM_TERMINATED, null, null),
                new StreamEventListener() {

                    public void gotStreamEvent(String event, Object source) {
                        if (rosterRetrieveTask != null)
                            rosterRetrieveTask.cancel();
                        valid_stream = false;
                        closeStream();
                        if (xmppListener != null)
                            xmppListener.connectionLost();
                        showAlert(AlertType.ERROR, "Connection lost", "Connection with the server lost", null);
                        BasicXmlStream.removeEventListener(lostConnReg);
                        /* XXX: should close all screens and open the RegisterScreen */
                    }
                });

        // if this is the first login it is better to ask the roster and go online suddenly
        // otherwise i only go online and the ask the roster after a while
        // however first try to load the roster from db
        String rmsName = "rstr_" + Contact.userhost(my_jid).replace('@', '_').replace('.', '_');
        if (rmsName.length() > 31) {
            rmsName = rmsName.substring(0, 31);
        }
        roster.rosterStore = new RMSIndex(rmsName);
        roster.readFromStorage();
        if (newCredentials || Integer.parseInt(this.roster.rosterVersion) > 0) {
            roster.retrieveRoster(true, false);
        } else {
            this.setPresence(-1, null);

            lastPresenceTime = System.currentTimeMillis();
            rosterRetrieveTask = new TimerTask() {
                public void run() {
                    if (lastPresenceTime + rosterRetrieveTime < System.currentTimeMillis()) {
                        roster.retrieveRoster(false, false);
                        this.cancel();
                    }
                }
            };
            Utils.tasks.schedule(rosterRetrieveTask, rosterRetrieveTime, rosterRetrieveTime);
        }
        if (this.xmppListener != null)
            xmppListener.authenticated();
    }

    private String getCapVer() {
        Vector ss = new Vector();
        ss.addElement("client/");
        ss.addElement("phone/");
        ss.addElement("/")/* XXX should be the lang here */;
        ss.addElement("Lampiro " + cfg.getProperty(Config.VERSION) + "<");
        for (int i = 0; i < features.length; i++) {
            ss.addElement(features[i]);
            ss.addElement("<");
        }
        Enumeration en = ss.elements();
        String S = "";
        while (en.hasMoreElements()) {
            S += en.nextElement();
        }
        S = new String(Base64.encode(Utils.digest(S, "sha1")));
        return S;
    }

    private class MessageHandler implements PacketListener {

        public void packetReceived(Element p) {

            // different behaviours depending on type

            String type = p.getAttribute(Message.ATT_TYPE);
            if (type == null)
                type = Message.NORMAL;

            // e.g. normal are used to receive invite for MUC;
            // if the MUC user is created here it result in a normal Contact!!!
            // be careful for that 
            Element x = p.getChildByName(XMPPClient.NS_MUC_USER, "x");
            if (x != null) {
                Element invite = x.getChildByName(null, "invite");
                if (invite != null)
                    return;
            }

            Message msg = new Message(p);
            // XXX: we will need to check the type
            String jid = msg.getAttribute(Stanza.ATT_FROM);
            // error packet sometimes do not have from
            if (jid == null)
                return;
            Contact u = roster.getContactByJid(jid);

            if (u == null) {
                Element group_elements[] = p.getChildrenByName(null, "group");
                String groups[] = new String[group_elements.length];
                for (int j = 0; j < groups.length; j++) {
                    groups[j] = group_elements[j].getText();
                }
                u = new Contact(Contact.userhost(jid), p.getAttribute("name"), p.getAttribute("subscription"),
                        groups);
                roster.contacts.put(Contact.userhost(u.jid), u);
            }

            u.addMessageToHistory(jid, msg);
            if (xmppListener != null)
                xmppListener.updateContact(u, Contact.CH_MESSAGE_NEW);
            playSmartTone();
        }
    }

    private class PresenceHandler implements PacketListener {

        public void packetReceived(Element e) {
            // #mdebug
            //@         Logger.log("PresenceHandler: received packet: "
            //@               + new String(e.toXml()), Logger.DEBUG);
            // #enddebug

            lastPresenceTime = System.currentTimeMillis();
            String t = e.getAttribute(Stanza.ATT_TYPE);
            if (t == null || Presence.T_UNAVAILABLE.equals(t)) {
                Presence p = new Presence(e);

                String from = e.getAttribute(Stanza.ATT_FROM);
                Contact u = roster.getContactByJid(from);
                String type = e.getAttribute(Stanza.ATT_TYPE);
                if (u == null) {
                    // first check if its a MUC
                    Element[] xs = p.getChildrenByName(null, "x");
                    for (int i = 0; xs != null && i < xs.length; i++) {
                        if (xs[i].uri != null && xs[i].uri.indexOf(XMPPClient.NS_MUC) >= 0) {
                            u = new MUC(Contact.userhost(from), Contact.user(from));
                        }
                    }

                    if (u == null) {
                        if (type != null && type.compareTo(Presence.T_UNAVAILABLE) == 0)
                            return;
                        // XXX Guess the subscription
                        u = new Contact(Contact.userhost(from), null, "unknown", null);
                    }

                    u.updatePresence(new Presence(e));
                    roster.contacts.put(u.jid, u);
                } else {
                    u.updatePresence(p);
                }
                if (xmppListener != null)
                    xmppListener.updateContact(u, Contact.CH_STATUS);

            } else if (Presence.T_SUBSCRIBE.equals(t)) {
                handleSubscribe(new Presence(e));
            } else {
                // XXX At present ignore other cases, but when receiving
                // UNSUBCRIBED we should update the roster
            }
        }

        private void handleSubscribe(Presence p) {
            // try getting the contact (we may already have it)
            String jid = Contact.userhost(p.getAttribute(Stanza.ATT_FROM));
            Contact u = roster.getContactByJid(jid);
            if (u == null) {
                // we don't have the contact, create it
                u = new Contact(jid, null, null, null);
            }

            // subscription handling
            if ("both".equals(u.subscription) || "to".equals(u.subscription)
                    || Config.LAMPIRO_AGENT.equals(Contact.userhost(jid))) {
                // subscribe received: if already granted, I don't ask anything
                Presence pmsg = new Presence();
                pmsg.setAttribute(Stanza.ATT_TO, u.jid);
                pmsg.setAttribute(Stanza.ATT_TYPE, Presence.T_SUBSCRIBED);
                sendPacket(pmsg);
            } else {
                /*
                 * UIMenu confirmMenu = new UIMenu(rm
                 * .getString(ResourceIDs.STR_SUBSCRIPTION_CONFIRM)); UILabel
                 * confirmQuestion = new UILabel(rm
                 * .getString(ResourceIDs.STR_SUBSCRIPTION_REQUEST_FROM) + " " +
                 * u.jid + ". " +
                 * rm.getString(ResourceIDs.STR_SUBSCRIPTION_ACCEPT));
                 * confirmMenu.append(confirmQuestion);
                 * confirmQuestion.setFocusable(true);
                 * confirmMenu.setSelectedIndex(1);
                 * confirmMenu.setAbsoluteX(10);
                 * confirmMenu.setWidth(UICanvas.getInstance().getWidth() - 20);
                 * confirmQuestion.setWrappable(true, confirmQuestion.getWidth() -
                 * 5); confirmMenu.cancelMenuString =
                 * rm.getString(ResourceIDs.STR_NO);
                 * confirmMenu.selectMenuString = rm
                 * .getString(ResourceIDs.STR_YES); UIScreen currentScreen =
                 * UICanvas.getInstance() .getCurrentScreen(); Graphics cg =
                 * currentScreen.getGraphics(); int offset = (cg.getClipHeight() -
                 * confirmMenu.getHeight(cg)) / 2;
                 * confirmMenu.setAbsoluteY(offset); this.confirmContact =
                 * contact; currentScreen.addPopup(confirmMenu);
                 */

                // add a nick only if a previous name has been added
                Element nick = p.getChildByName(XMPPClient.NS_NICK, "nick");
                if (nick != null) {
                    String nickNameText = nick.getText();
                    if (nickNameText != null && nickNameText.length() > 0
                            && (u.name == null || u.name.length() == 0)) {
                        u.name = nickNameText;
                    }
                }

                Enumeration en = XMPPClient.this.autoAcceptGateways.elements();
                while (en.hasMoreElements()) {
                    String ithGateway = (String) en.nextElement();
                    if (u.jid.indexOf(ithGateway) >= 0) {
                        XMPPClient.this.roster.subscribeContact(u, true);
                        return;
                    }
                }
                if (xmppListener != null) {
                    xmppListener.askSubscription(u);
                }
            }
        }
    }

    // XXX The roster handler should always listen to any iq:roster packet for
    // supporting roster push and any roster update when logged in

    private class DataFormHandler implements PacketListener {

        public void packetReceived(Element p) {
            updateTask(new SimpleDataFormExecutor(p));
            playSmartTone();
        }
    }

    private class DiscoHandler implements PacketListener {

        public void packetReceived(Element p) {
            Element q = p.getChildByName(NS_IQ_DISCO_INFO, Iq.QUERY);
            Iq reply = new Iq(p.getAttribute(Stanza.ATT_FROM), Iq.T_RESULT);
            reply.setAttribute(Stanza.ATT_ID, p.getAttribute(Stanza.ATT_ID));
            String node = q.getAttribute("node");
            Element qr = reply.addElement(NS_IQ_DISCO_INFO, Iq.QUERY);
            String capDisco = XMPPClient.NS_BLUENDO_CAPS + "#" + XMPPClient.this.getCapVer();
            if (node == null || node.compareTo(capDisco) == 0) {
                Element identity = qr.addElement(NS_IQ_DISCO_INFO, "identity");
                identity.setAttribute("category", "client");
                identity.setAttribute("type", "phone");
                identity.setAttribute("name", "Lampiro");
                for (int i = 0; i < features.length; i++) {
                    Element feature = qr.addElement(NS_IQ_DISCO_INFO, "feature");
                    feature.setAttribute("var", features[i]);
                }

            } else if (MIDP_PLATFORM.equals(node)) {
                qr.setAttribute("node", MIDP_PLATFORM);
                Element x = qr.addElement(JABBER_X_DATA, "x");
                x.setAttribute("type", Iq.T_RESULT);
                Element field = x.addElement(JABBER_X_DATA, "field");
                field.setAttribute("var", "microedition.platform");
                field.addElement(JABBER_X_DATA, "value").addText(System.getProperty("microedition.platform"));
            }
            if (node != null && node.compareTo(capDisco) == 0) {
                qr.setAttribute("node", capDisco);
            }
            sendPacket(reply);
        }
    }

    public void playSmartTone() {
        if (this.xmppListener != null) {
            xmppListener.playSmartTone();
        }
    }

    /*
     * Set the current priority of the client (store and send it).
     * After setting the priority calls setpresence.
     * 
     * @param priority The priority to set
     */
    public void setPresence(int availability, String status, int priority) {
        Presence p = me.getPresence(my_jid);
        p.setPriority(priority);
        this.setPresence(availability, status);
    }

    /**
     * Set the current presence of the client (store and send it)
     * 
     * @param availability
     * @param status
     */
    public void setPresence(int availability, String status) {

        Presence p = me.getPresence(my_jid);
        if (p == null)
            return;
        Presence new_p = new Presence();

        new_p.setAttribute("from", p.getAttribute("from"));

        if (availability >= 0) {
            if (Contact.AV_ONLINE == availability) {

            } else if (Contact.AV_UNAVAILABLE == availability) {
                new_p.setAttribute(Stanza.ATT_TYPE, Presence.T_UNAVAILABLE);
            } else {
                new_p.setShow(Contact.availability_mapping[availability]);
            }
        }

        if (status != null) {
            new_p.setStatus(status);
        } else {
            new_p.setStatus(p.getStatus());
        }

        //new_p.addElement(p.getChildByName(null, "x"));
        new_p.addElement(p.getChildByName(null, "c"));
        new_p.setPriority(p.getPriority());
        me.updatePresence(new_p);
        sendPacket(new_p);
        if (Presence.T_UNAVAILABLE.equals(new_p.getAttribute(Stanza.ATT_TYPE))) {
            closeStream();
        }
    }

    /**
     * Handle an incoming command list
     * 
     * @param e
     *            the received element with commands
     * @param show
     *            when true show the command list screen
     */
    public void handleClientCommands(Element e, boolean show) {
        String from = e.getAttribute(Stanza.ATT_FROM);
        if (from == null)
            return;
        Contact c = roster.getContactByJid(from);
        if (c == null)
            return;
        Element q = e.getChildByName("http://jabber.org/protocol/disco#items", Iq.QUERY);
        if (q != null) {
            Element items[] = q.getChildrenByName("http://jabber.org/protocol/disco#items", "item");
            c.cmdlist = new String[items.length][2];
            for (int i = 0; i < items.length; i++) {
                c.cmdlist[i][0] = items[i].getAttribute("node");
                c.cmdlist[i][1] = items[i].getAttribute("name");
            }

            if (xmppListener != null)
                xmppListener.updateContact(c, Contact.CH_TASK_NEW);

            if (show && this.xmppListener != null) {
                this.xmppListener.handleCommand(c, from);
            }
        } // XXX we could add an alert if it's empty and we have to show
    }

    /**
     * Show an error screen, if multiple errors occur only append the message
     * 
     * @param type
     * @param title
     *            Title of the screen
     * @param text
     *            Displayed error message
     * @param next_screen
     *            the screen where we have to return to
     */
    public void showAlert(AlertType type, String title, String text, final Object next_screen) {
        if (xmppListener != null) {
            xmppListener.showAlert(type, title, text, next_screen);
        }
    }

    /**
     * Update the status of a task. Queue it if this is the first time it's
     * status is updated
     * 
     * @param task
     */
    public void updateTask(Task task) {

        Contact user = roster.getContactByJid(task.getFrom());
        user.addTask(task);
        // #mdebug
        //@      System.out.println("Tsk: " + Integer.toHexString(task.getStatus()));
        //#enddebug
        byte type = task.getStatus();

        // true if we should display the command
        boolean display = false;
        boolean removed = false;

        if ((type & Task.CMD_MASK) == Task.CMD_MASK) {
            switch (type) {
            case Task.CMD_FORM_LESS:
                display = false;
                removed = true;
                user.removeTask(task);
                break;
            case Task.CMD_INPUT:
                display = true;
                break;
            case Task.CMD_EXECUTING:
                // do nothing, just wait for an answer
                break;
            case Task.CMD_CANCELING:
                // do nothing, just wait for an answer
                break;
            case Task.CMD_CANCELED:
                display = true;
                removed = true;
                user.removeTask(task);
                break;
            case Task.CMD_FINISHED:
                // tasks.removeElement(task);
                display = true;
                break;
            case Task.CMD_ERROR:
                display = true;
                removed = true;
                break;
            case Task.CMD_DESTROY:
                removed = true;
                user.removeTask(task);
                break;
            }
        } else { // simple data form
            switch (type) {
            case Task.DF_FORM:
                display = true;
                break;
            case Task.DF_SUBMITTED:
                removed = true;
                user.removeTask(task);
                break;
            case Task.DF_CANCELED:
                removed = true;
                user.removeTask(task);
                break;
            case Task.DF_RESULT:
                display = true;
                break;
            case Task.DF_ERROR:
                display = true;
                removed = true;
                break;
            case Task.DF_DESTROY:
                removed = true;
                user.removeTask(task);
                break;
            }
        }

        // update contact position in the roster
        if (xmppListener != null) {
            if (removed) {
                xmppListener.updateContact(user, Contact.CH_TASK_REMOVED);
            } else {
                xmppListener.updateContact(user, Contact.CH_TASK_NEW);
            }
        }

        if (this.xmppListener != null) {
            this.xmppListener.handleTask(task, display);
        }
    };

    // private void subscribe_to_agent() {
    // if(lampiro_subscribe_sent){
    // return;
    // }
    // lampiro_subscribe_sent = true;
    //      
    // Contact c = getContactByJid(Config.LAMPIRO_AGENT);
    // if(c == null) {
    // c = new Contact(Config.LAMPIRO_AGENT, "Lampiro Agent", "none", null);
    // subscribeContact(c);
    // } else if(!"both".equals(c.subscription)) {
    // // XXX resend presesence of type subscribe!
    // }
    // }

    public Roster getRoster() {
        return roster;
    }

    public void setXmppListener(XMPPClient.XmppListener xmppListener) {
        this.xmppListener = xmppListener;
    }

    public XMPPClient.XmppListener getXmppListener() {
        return xmppListener;
    }

    public static String getErrorString(String code) {
        for (int i = 0; i < XMPPClient.errorCodes.length; i++) {
            String[] ithCode = XMPPClient.errorCodes[i];
            if (ithCode[0].equals(code)) {
                return (ithCode[1]);
            }
        }
        return "";
    }

    // #ifdef SEND_DEBUG1
    public void sendDebug(String msg) {
        Message m = new Message("ff@jabber.bluendo.com", "chat");
        m.setBody(msg);
        sendPacket(m);
    }
    // #endif

}