@Override
  protected void onStart() {
    super.onStart();

    if (mKey == null) {
      PersonalKeyRunnable action =
          new PersonalKeyRunnable() {
            public void run(PersonalKey key) {
              mKey = key;
              if (mValidator != null)
                // this will release the waiting lock
                mValidator.setKey(mKey);
            }
          };

      // random passphrase (40 characters!!!!)
      mPassphrase = StringUtils.randomString(40);

      mKeyReceiver = new KeyGeneratedReceiver(mHandler, action);

      IntentFilter filter = new IntentFilter(KeyPairGeneratorService.ACTION_GENERATE);
      lbm.registerReceiver(mKeyReceiver, filter);

      Toast.makeText(this, R.string.msg_generating_keypair, Toast.LENGTH_LONG).show();

      Intent i = new Intent(this, KeyPairGeneratorService.class);
      i.setAction(KeyPairGeneratorService.ACTION_GENERATE);
      startService(i);
    }
  }
Ejemplo n.º 2
0
  public void sendMessage(String text) {
    final Message message = new Message();

    if (threadID == null) {
      threadID = StringUtils.randomString(6);
    }
    message.setThread(threadID);

    // Set the body of the message using typedMessage
    message.setBody(text);

    // IF there is no body, just return and do nothing
    if (!ModelUtil.hasLength(text)) {
      return;
    }

    // Fire Message Filters
    SparkManager.getChatManager().filterOutgoingMessage(this, message);

    // Fire Global Filters
    SparkManager.getChatManager().fireGlobalMessageSentListeners(this, message);

    sendMessage(message);

    sendNotification = true;
  }
/** @author Kir */
public class JabberTransport implements Transport, ConnectionListener, Disposable {
  @NonNls private static final Logger LOG = Logger.getLogger(JabberTransport.class);

  private static final int RESPONSE_TIMEOUT = 120 * 1000;
  @NonNls public static final String CODE = "Jabber";

  private final JabberUI myUI;
  private final JabberFacade myFacade;
  private final UserModel myUserModel;
  private final IDEtalkListener myUserModelListener = new MyUserModelListener();
  private final AsyncMessageDispatcher myDispatcher;

  private RosterListener myRosterListener;
  private PacketListener mySubscribeListener;
  private PacketListener myMessageListener;
  private final JabberUserFinder myUserFinder;
  private final IDEFacade myIdeFacade;

  private final String myThreadIdPrefix = StringUtils.randomString(5);
  private int myCurrentThreadId;
  private boolean myIgnoreUserEvents;
  private PresenceMode myPresenceMode;

  private final Map<User, UserPresence> myUser2Presence = new HashMap<>();
  private final Set<String> myIDEtalkUsers = new HashSet<>();
  private final Map<String, String> myUser2Thread =
      Collections.synchronizedMap(new HashMap<String, String>());

  @NonNls private static final String RESPONSE = "response";
  private final IgnoreList myIgnoreList;

  // negative value disables reconnect
  private int myReconnectTimeout =
      Integer.parseInt(System.getProperty("ideTalk.reconnect", "30")) * 1000;
  private Future<?> myReconnectProcess;

  public JabberTransport(
      JabberUI UI,
      JabberFacade facade,
      UserModel userModel,
      AsyncMessageDispatcher messageDispatcher,
      JabberUserFinder userFinder) {
    Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);

    // XMPPConnection.DEBUG_ENABLED = true;
    JDOMExtension.init();

    myUI = UI;
    myFacade = facade;
    myUserModel = userModel;
    myDispatcher = messageDispatcher;
    myUserFinder = userFinder;
    myIdeFacade = messageDispatcher.getIdeFacade();
    myIgnoreList = new IgnoreList(myIdeFacade);

    myFacade.addConnectionListener(this);
    getBroadcaster().addListener(myUserModelListener);
  }

  private EventBroadcaster getBroadcaster() {
    return myUserModel.getBroadcaster();
  }

  @Override
  public String getName() {
    return CODE;
  }

  @Override
  public void initializeProject(String projectName, MutablePicoContainer projectLevelContainer) {
    myUI.initPerProject(projectLevelContainer);
    myIdeFacade.runOnPooledThread(() -> myFacade.connect());
  }

  @Override
  public User[] findUsers(ProgressIndicator progressIndicator) {
    if (isOnline()) {
      return myUserFinder.findUsers(progressIndicator);
    }
    return new User[0];
  }

  @Override
  public Class<? extends NamedUserCommand> getSpecificFinderClass() {
    return FindByJabberIdCommand.class;
  }

  @Override
  public boolean isOnline() {
    return myFacade.isConnectedAndAuthenticated();
  }

  @Override
  public UserPresence getUserPresence(User user) {
    UserPresence presence = myUser2Presence.get(user);
    if (presence == null) {
      presence = new UserPresence(false);
      myUser2Presence.put(user, presence);
    }
    return presence;
  }

  private UserPresence _getUserPresence(User user) {
    Presence presence = _getPresence(user);
    if (presence != null && presence.getType() == Presence.Type.available) {
      Presence.Mode mode = presence.getMode();
      final PresenceMode presenceMode;
      //noinspection IfStatementWithTooManyBranches
      if (mode == Presence.Mode.away) {
        presenceMode = PresenceMode.AWAY;
      } else if (mode == Presence.Mode.dnd) {
        presenceMode = PresenceMode.DND;
      } else if (mode == Presence.Mode.xa) {
        presenceMode = PresenceMode.EXTENDED_AWAY;
      } else {
        presenceMode = PresenceMode.AVAILABLE;
      }

      return new UserPresence(presenceMode);
    }
    return new UserPresence(false);
  }

  @Override
  @NonNls
  public Icon getIcon(UserPresence userPresence) {
    return UIUtil.getIcon(
        userPresence, IdetalkCoreIcons.IdeTalk.Jabber, IdetalkCoreIcons.IdeTalk.Jabber_dnd);
  }

  @Override
  public boolean isSelf(User user) {
    return myFacade.isConnectedAndAuthenticated()
        && getSimpleId(myFacade.getConnection().getUser()).equals(user.getName());
  }

  @Override
  public String[] getProjects(User user) {
    return ArrayUtil.EMPTY_STRING_ARRAY;
  }

  @Override
  @Nullable
  public String getAddressString(User user) {
    return null;
  }

  @Override
  public synchronized void sendXmlMessage(User user, final XmlMessage xmlMessage) {
    if (!myUI.connectAndLogin(null)) {
      return;
    }

    final String threadId = getThreadId(user);
    final PacketCollector packetCollector =
        myFacade.getConnection().createPacketCollector(new ThreadFilter(threadId));
    doSendMessage(xmlMessage, user, threadId);

    if (xmlMessage.needsResponse()) {
      //noinspection HardCodedStringLiteral
      final Runnable responseWaiterRunnable =
          () -> {
            try {
              processResponse(xmlMessage, packetCollector);
            } finally {
              packetCollector.cancel();
            }
          };
      myIdeFacade.runOnPooledThread(responseWaiterRunnable);
    } else {
      packetCollector.cancel();
    }
  }

  String getThreadId(User user) {
    String id = myUser2Thread.get(user.getName());
    if (id == null) {
      id = myThreadIdPrefix + myCurrentThreadId++;
      myUser2Thread.put(user.getName(), id);
    }
    return id;
  }

  @Override
  public void setOwnPresence(UserPresence userPresence) {
    if (isOnline() && !userPresence.isOnline()) {
      myFacade.disconnect();
    } else if (!isOnline() && userPresence.isOnline()) {
      myUI.connectAndLogin(null);
    }

    if (isOnline() && presenceModeChanged(userPresence.getPresenceMode())) {
      myFacade.setOnlinePresence(userPresence);
      myPresenceMode = userPresence.getPresenceMode();
    }
  }

  @Override
  public boolean hasIdeTalkClient(User user) {
    return myIDEtalkUsers.contains(user.getName());
  }

  private boolean presenceModeChanged(PresenceMode presenceMode) {
    return myPresenceMode == null || myPresenceMode != presenceMode;
  }

  private static void processResponse(XmlMessage xmlMessage, PacketCollector collector) {
    boolean gotResponse = false;

    while (!gotResponse) {

      Message response = (Message) collector.nextResult(RESPONSE_TIMEOUT);
      if (response == null) break;

      final Collection<PacketExtension> extensions = response.getExtensions();
      for (PacketExtension o : extensions) {
        if (o instanceof JDOMExtension) {
          JDOMExtension extension = (JDOMExtension) o;
          if (RESPONSE.equals(extension.getElement().getName())) {
            xmlMessage.processResponse(extension.getElement());
            gotResponse = true;
            break;
          }
        }
      }
    }
  }

  private Message doSendMessage(XmlMessage xmlMessage, User user, String threadId) {
    Element element = new Element(xmlMessage.getTagName(), xmlMessage.getTagNamespace());
    xmlMessage.fillRequest(element);

    Message message = createBaseMessage(user, element.getText());
    message.setThread(threadId);
    message.addExtension(new JDOMExtension(element));
    myFacade.getConnection().sendPacket(message);

    return message;
  }

  static Message createBaseMessage(User user, String message) {
    Message msg = new Message(user.getName(), Message.Type.CHAT);
    msg.setBody(message);
    return msg;
  }

  @Override
  public void connected(XMPPConnection connection) {
    LOG.info("Jabber connected");
    if (mySubscribeListener == null) {
      mySubscribeListener = new MySubscribeListener();
      connection.addPacketListener(mySubscribeListener, new PacketTypeFilter(Presence.class));
    }
    if (myMessageListener == null) {
      myMessageListener = new MyMessageListener();
      connection.addPacketListener(myMessageListener, new PacketTypeFilter(Message.class));
    }
  }

  @Override
  public void authenticated() {
    LOG.info("Jabber authenticated: " + myFacade.getConnection().getUser());
    if (myRosterListener == null) {
      myRosterListener = new MyRosterListener();
      getRoster().addRosterListener(myRosterListener);
    }
    myUserFinder.registerForProject(myFacade.getMyAccount().getJabberId());

    if (!hasJabberContacts()) {
      synchronizeRoster(false);
    }
  }

  private boolean hasJabberContacts() {
    User[] users = myUserModel.getAllUsers();
    for (User user : users) {
      if (user.getTransportCode().equals(getName())) return true;
    }
    return false;
  }

  @Override
  public void disconnected(boolean onError) {
    final XMPPConnection connection = myFacade.getConnection();

    LOG.info("Jabber disconnected: " + connection.getUser());
    connection.removePacketListener(mySubscribeListener);
    mySubscribeListener = null;
    connection.removePacketListener(myMessageListener);
    myMessageListener = null;

    final Roster roster = connection.getRoster();
    if (roster != null) {
      roster.removeRosterListener(myRosterListener);
    }
    myRosterListener = null;

    myIDEtalkUsers.clear();
    myUser2Presence.clear();
    myUser2Thread.clear();

    if (onError && reconnectEnabledAndNotStarted()) {
      LOG.warn(getMsg("jabber.server.was.disconnected", myReconnectTimeout / 1000));
      myReconnectProcess = myIdeFacade.runOnPooledThread(new MyReconnectRunnable());
    }
  }

  private boolean reconnectEnabledAndNotStarted() {
    return (myReconnectProcess == null || myReconnectProcess.isDone()) && myReconnectTimeout >= 0;
  }

  @Override
  public void dispose() {
    getBroadcaster().removeListener(myUserModelListener);
    myFacade.removeConnectionListener(this);
  }

  private void updateUserPresence(String jabberId) {
    LOG.debug("Presence changed for " + jabberId);
    final User user = myUserModel.findUser(getSimpleId(jabberId), getName());
    if (user != null) {
      updateIsIDEtalkClient(jabberId, user);

      final UserPresence presence = _getUserPresence(user);
      IDEtalkEvent event = createPresenceChangeEvent(user, presence);
      if (event != null) {
        getBroadcaster().doChange(event, () -> myUser2Presence.put(user, presence));
      }
    }
  }

  private void updateIsIDEtalkClient(String jabberId, User user) {
    if (getResource(jabberId)
        .toLowerCase()
        .startsWith(JabberFacade.IDETALK_RESOURCE.toLowerCase())) {
      myIDEtalkUsers.add(user.getName());
    } else {
      myIDEtalkUsers.remove(user.getName());
    }
  }

  @Nullable
  private IDEtalkEvent createPresenceChangeEvent(User user, UserPresence newPresence) {
    UserPresence oldPresence = getUserPresence(user);
    if (!newPresence.equals(oldPresence)) {
      if (newPresence.isOnline() ^ oldPresence.isOnline()) {
        return newPresence.isOnline() ? new UserEvent.Online(user) : new UserEvent.Offline(user);
      } else {
        return new UserEvent.Updated(
            user, PRESENCE, oldPresence.getPresenceMode(), newPresence.getPresenceMode());
      }
    }
    return null;
  }

  private void updateJabberUsers(boolean removeUsersNotInRoster) {
    LOG.debug("Roster changed - update user model");
    Set<User> currentUsers = new HashSet<>(Arrays.asList(myUserModel.getAllUsers()));
    for (RosterEntry rosterEntry : getRoster().getEntries()) {
      User user = addJabberUserToUserModelOrUpdateInfo(rosterEntry);
      currentUsers.remove(user);
    }

    if (removeUsersNotInRoster) {
      removeUsers(currentUsers);
    }

    if (LOG.isDebugEnabled()) {
      LOG.debug("Roster synchronized: " + Arrays.asList(myUserModel.getAllUsers()));
    }
  }

  private void removeUsers(Set<User> currentUsers) {
    for (User user : currentUsers) {
      myUserModel.removeUser(user);
    }
  }

  private User addJabberUserToUserModelOrUpdateInfo(RosterEntry rosterEntry) {
    //    System.out.println("rosterEntry.getName() = " + rosterEntry.getName());
    //    System.out.println("rosterEntry.getUser() = " + rosterEntry.getUser());
    User user = myUserModel.createUser(getSimpleId(rosterEntry.getUser()), getName());
    String newGroup = getUserGroup(rosterEntry);
    if (newGroup != null) {
      user.setGroup(newGroup, myUserModel);
    }
    user.setDisplayName(rosterEntry.getName(), myUserModel);
    myUserModel.addUser(user);
    String jabberId = getCurrentJabberID(user, rosterEntry);
    updateIsIDEtalkClient(jabberId, user);
    return user;
  }

  private String getCurrentJabberID(User user, RosterEntry rosterEntry) {
    Presence presence = _getPresence(user);
    String jabberId = null;
    if (presence != null) {
      jabberId = presence.getFrom();
    }
    if (jabberId == null) jabberId = rosterEntry.getUser();
    if (jabberId == null) jabberId = rosterEntry.getName();
    return jabberId;
  }

  static String getResource(String userName) {
    int lastSlash = userName.indexOf('/');
    if (lastSlash != -1) {
      return userName.substring(lastSlash + 1);
    }
    return "";
  }

  static String getSimpleId(String userName) {
    String id = userName;
    int lastSlash = id.indexOf('/');
    if (lastSlash != -1) {
      id = id.substring(0, lastSlash);
    }
    return id;
  }

  @Nullable
  private static String getUserGroup(RosterEntry rosterEntry) {
    String group = null;
    for (RosterGroup rosterGroup : rosterEntry.getGroups()) {
      group = rosterGroup.getName();
    }
    return group;
  }

  private Roster getRoster() {
    final Roster roster = myFacade.getConnection().getRoster();
    assert roster != null;
    return roster;
  }

  public JabberFacade getFacade() {
    return myFacade;
  }

  boolean isUserInMyContactListAndActive(String userName) {
    User user = myUserModel.findUser(getSimpleId(userName), getName());
    return user != null && user.isOnline();
  }

  @Nullable
  private Presence _getPresence(User user) {
    if (!isOnline()) return null;
    return getRoster().getPresence(user.getName());
  }

  private User self() {
    return myUserModel.createUser(myFacade.getMyAccount().getJabberId(), getName());
  }

  public static JabberTransport getInstance() {
    return (JabberTransport) Pico.getInstance().getComponentInstanceOfType(JabberTransport.class);
  }

  public void synchronizeRoster(boolean removeUsersNotInRoster) {
    updateJabberUsers(removeUsersNotInRoster);
  }

  public void runIngnoringUserEvents(Runnable runnable) {
    try {
      myIgnoreUserEvents = true;
      runnable.run();
    } finally {
      myIgnoreUserEvents = false;
    }
  }

  /** -1 disables reconnect */
  public void setReconnectTimeout(int milliseconds) {
    myReconnectTimeout = milliseconds;
  }

  private class MyRosterListener implements RosterListener {
    @Override
    public void entriesAdded(Collection addresses) {
      updateJabberUsers(false);
    }

    @Override
    public void entriesUpdated(Collection addresses) {
      updateJabberUsers(false);
    }

    @Override
    public void entriesDeleted(Collection addresses) {
      updateJabberUsers(false);
    }

    @Override
    public void presenceChanged(final String string) {
      updateUserPresence(string);
    }
  }

  @SuppressWarnings({"RefusedBequest"})
  private class MyUserModelListener extends TransportUserListener {

    MyUserModelListener() {
      super(JabberTransport.this);
    }

    @Override
    protected void processBeforeChange(UserEvent event) {
      super.processBeforeChange(event);
      event.accept(
          new EventVisitor() {
            @Override
            public void visitUserAdded(UserEvent.Added event) {
              event.getUser().setCanAccessMyFiles(false, myUserModel);
            }
          });
    }

    @Override
    protected void processAfterChange(UserEvent event) {
      if (myIgnoreUserEvents) return;

      event.accept(
          new EventVisitor() {
            @Override
            public void visitUserRemoved(UserEvent.Removed event) {
              synchronizeWithJabberIfPossible(event);
            }

            @Override
            public void visitUserUpdated(UserEvent.Updated event) {
              if (GROUP.equals(event.getPropertyName())
                  || DISPLAY_NAME.equals(event.getPropertyName())) {
                synchronizeWithJabberIfPossible(event);
              }
            }
          });
    }

    private void synchronizeWithJabberIfPossible(UserEvent event) {
      if (event.getUser().getTransportCode().equals(getName())
          && myFacade.isConnectedAndAuthenticated()) {
        myDispatcher.sendNow(self(), new JabberSyncUserMessage(event));
      }
    }
  }

  private class MySubscribeListener implements PacketListener {
    @Override
    public void processPacket(Packet packet) {
      final Presence presence = ((Presence) packet);
      if (presence.getType() != Presence.Type.subscribe) return;
      LOG.info("Subscribe request from " + presence.getFrom());

      if (myIgnoreList.isIgnored(presence.getFrom())) {
        LOG.info(presence.getFrom() + " in ignore list");
        return;
      }

      if (isUserInMyContactListAndActive(presence.getFrom()) || Pico.isUnitTest()) {
        acceptSubscription(presence, true);
        return;
      }

      UIUtil.invokeLater(
          () -> acceptSubscription(presence, myUI.shouldAcceptSubscriptionRequest(presence)));
    }

    private void acceptSubscription(final Presence presence, boolean subscribe) {
      if (!isOnline()) return;

      myFacade.changeSubscription(presence.getFrom(), subscribe);

      if (subscribe) {
        String from = getSimpleId(presence.getFrom());
        LOG.info("Add " + from + " to the roster");

        try {
          getRoster().createEntry(from, from, new String[] {UserModel.DEFAULT_GROUP});
        } catch (XMPPException e) {
          LOG.warn(e);
        }
      }
    }
  }

  private class MyMessageListener implements PacketListener {

    @Override
    public void processPacket(Packet packet) {
      try {
        doProcessPacket(packet);
      } catch (Throwable e) {
        LOG.error(e.getMessage(), e);
      }
    }

    private void doProcessPacket(Packet packet) {
      final Message message = ((Message) packet);
      if (message.getType() == Message.Type.ERROR) {
        UIUtil.invokeLater(
            () -> {
              String from =
                  (message.getFrom() != null) ? getMsg("from.0.lf", message.getFrom()) : "";
              LOG.warn(
                  getMsg(
                      "jabber.error.text",
                      from,
                      (message.getError() == null ? "N/A" : message.getError().toString())));
            });
        return;
      }

      if (myIgnoreList.isIgnored(packet.getFrom())) {
        return;
      }

      Element element = null;
      for (PacketExtension o : message.getExtensions()) {
        if (o instanceof JDOMExtension) {
          element = ((JDOMExtension) o).getElement();
        }
      }

      if (element != null && !RESPONSE.equals(element.getName())) {
        processAndSendResponse(element, message);
      } else if (element == null && message.getBody() != null) {
        // Some simple Jabber Message
        MessageEvent event =
            EventFactory.createMessageEvent(
                JabberTransport.this, getFrom(message), message.getBody());
        if (message.getThread() != null) {
          myUser2Thread.put(getFrom(message), message.getThread());
        }

        getBroadcaster().fireEvent(event);
      }
    }

    private void processAndSendResponse(Element element, Message message) {
      Element response = new Element(RESPONSE, Transport.NAMESPACE);
      XmlResponseProvider provider = XmlResponseProvider.getProvider(element, getBroadcaster());
      if (provider.processAndFillResponse(
          response, element, JabberTransport.this, getFrom(message))) {
        Message responseMessage = new Message(getFrom(message));
        responseMessage.addExtension(new JDOMExtension(response));
        responseMessage.setThread(message.getThread());
        myFacade.getConnection().sendPacket(responseMessage);
      }
    }

    private String getFrom(Message message) {
      return getSimpleId(message.getFrom());
    }
  }

  private class MyReconnectRunnable implements Runnable {
    @Override
    public void run() {
      try {
        Thread.sleep(myReconnectTimeout);

        if (myFacade.connect() != null && myFacade.getMyAccount().isLoginAllowed()) {
          myReconnectProcess = myIdeFacade.runOnPooledThread(this);
        }
      } catch (InterruptedException ignored) {
        // return
      }
    }
  }
}
Ejemplo n.º 4
0
public class ChatSessionAdapter extends org.awesomeapp.messenger.service.IChatSession.Stub {

  private static final String NON_CHAT_MESSAGE_SELECTION =
      Imps.Messages.TYPE
          + "!="
          + Imps.MessageType.INCOMING
          + " AND "
          + Imps.Messages.TYPE
          + "!="
          + Imps.MessageType.OUTGOING;

  /** The registered remote listeners. */
  final RemoteCallbackList<IChatListener> mRemoteListeners =
      new RemoteCallbackList<IChatListener>();

  ImConnectionAdapter mConnection;
  ChatSessionManagerAdapter mChatSessionManager;

  ChatSession mChatSession;
  ListenerAdapter mListenerAdapter;
  boolean mIsGroupChat;
  StatusBarNotifier mStatusBarNotifier;

  private ContentResolver mContentResolver;
  /*package*/ Uri mChatURI;

  private Uri mMessageURI;

  private boolean mConvertingToGroupChat;

  private static final int MAX_HISTORY_COPY_COUNT = 10;

  private HashMap<String, Integer> mContactStatusMap = new HashMap<String, Integer>();

  private boolean mHasUnreadMessages;

  private RemoteImService service = null;

  private HashMap<String, OtrChatSessionAdapter> mOtrChatSessions;
  private SessionStatus mLastSessionStatus = null;
  private OtrDataHandler mDataHandler;

  private IDataListener mDataListener;
  private DataHandlerListenerImpl mDataHandlerListener;

  private boolean mAcceptTransfer = false;
  private boolean mWaitingForResponse = false;
  private boolean mAcceptAllTransfer = true; // TODO set this via preference, but default true
  private String mLastFileUrl = null;

  private long mContactId;

  public ChatSessionAdapter(
      ChatSession chatSession, ImConnectionAdapter connection, boolean isNewSession) {

    mChatSession = chatSession;
    mConnection = connection;

    service = connection.getContext();
    mContentResolver = service.getContentResolver();
    mStatusBarNotifier = service.getStatusBarNotifier();
    mChatSessionManager = (ChatSessionManagerAdapter) connection.getChatSessionManager();

    mListenerAdapter = new ListenerAdapter();

    mOtrChatSessions = new HashMap<String, OtrChatSessionAdapter>();

    ImEntity participant = mChatSession.getParticipant();

    if (participant instanceof ChatGroup) {
      init((ChatGroup) participant, isNewSession);
    } else {
      init((Contact) participant, isNewSession);
    }

    initOtrChatSession(participant);
  }

  private void initOtrChatSession(ImEntity participant) {
    try {
      if (mConnection != null) {
        mDataHandler = new OtrDataHandler(mChatSession);
        mDataHandlerListener = new DataHandlerListenerImpl();
        mDataHandler.setDataListener(mDataHandlerListener);

        OtrChatManager cm = service.getOtrChatManager();
        cm.addOtrEngineListener(mListenerAdapter);
        mChatSession.setMessageListener(new OtrChatListener(cm, mListenerAdapter));

        if (participant instanceof Contact) {
          String key = participant.getAddress().getAddress();
          if (!mOtrChatSessions.containsKey(key)) {
            OtrChatSessionAdapter adapter =
                new OtrChatSessionAdapter(
                    mConnection.getLoginUser().getAddress().getAddress(), participant, cm);
            mOtrChatSessions.put(key, adapter);
          }
        } else if (participant instanceof ChatGroup) {
          ChatGroup group = (ChatGroup) mChatSession.getParticipant();

          for (Contact contact : group.getMembers()) {
            String key = contact.getAddress().getAddress();
            if (!mOtrChatSessions.containsKey(key)) {
              OtrChatSessionAdapter adapter =
                  new OtrChatSessionAdapter(
                      mConnection.getLoginUser().getAddress().getAddress(), contact, cm);
              mOtrChatSessions.put(key, adapter);
            }
          }
        }

        mDataHandler.setChatId(getId());
      }
    } catch (NullPointerException npe) {
      Log.e(ImApp.LOG_TAG, "error init OTR session", npe);
    }
  }

  public synchronized IOtrChatSession getDefaultOtrChatSession() {

    if (mOtrChatSessions.size() > 0)
      return mOtrChatSessions.entrySet().iterator().next().getValue();
    else return null;
  }

  public void presenceChanged(int newPresence) {

    if (mChatSession.getParticipant() instanceof Contact) {
      ((Contact) mChatSession.getParticipant()).getPresence().setStatus(newPresence);

      try {
        if (newPresence == Presence.AVAILABLE) {

          if (hasPostponedMessages()) {
            if (getDefaultOtrChatSession().isChatEncrypted()) sendPostponedMessages();
            else {
              // getDefaultOtrChatSession().stopChatEncryption();
              getDefaultOtrChatSession().startChatEncryption();
            }
          }
        }

      } catch (RemoteException re) {
        // nothing to see here
      }
    }
  }

  private void init(ChatGroup group, boolean isNewSession) {

    mIsGroupChat = true;

    mContactId = insertOrUpdateGroupContactInDb(group);
    group.addMemberListener(mListenerAdapter);

    try {
      mChatSessionManager
          .getChatGroupManager()
          .joinChatGroupAsync(group.getAddress(), group.getName());

      mMessageURI = Imps.Messages.getContentUriByThreadId(mContactId);

      mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mContactId);

      if (isNewSession) insertOrUpdateChat("");

      for (Contact c : group.getMembers()) {
        mContactStatusMap.put(c.getName(), c.getPresence().getStatus());
      }

    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  private void init(Contact contact, boolean isNewSession) {
    mIsGroupChat = false;
    ContactListManagerAdapter listManager =
        (ContactListManagerAdapter) mConnection.getContactListManager();

    mContactId = listManager.queryOrInsertContact(contact);

    mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mContactId);

    if (isNewSession) insertOrUpdateChat(null);

    mMessageURI = Imps.Messages.getContentUriByThreadId(mContactId);

    mContactStatusMap.put(contact.getName(), contact.getPresence().getStatus());
  }

  public void reInit() {
    // insertOrUpdateChat(null);

  }

  private ChatGroupManager getGroupManager() {
    return mConnection.getAdaptee().getChatGroupManager();
  }

  public ChatSession getAdaptee() {
    return mChatSession;
  }

  public Uri getChatUri() {
    return mChatURI;
  }

  public String[] getParticipants() {
    if (mIsGroupChat) {
      Contact self = mConnection.getLoginUser();
      ChatGroup group = (ChatGroup) mChatSession.getParticipant();
      List<Contact> members = group.getMembers();
      String[] result = new String[members.size() - 1];
      int index = 0;
      for (Contact c : members) {
        if (!c.equals(self)) {
          result[index++] = c.getAddress().getAddress();
        }
      }
      return result;
    } else {

      return new String[] {mChatSession.getParticipant().getAddress().getAddress()};
    }
  }

  /**
   * Convert this chat session to a group chat. If it's already a group chat, nothing will happen.
   * The method works in async mode and the registered listener will be notified when it's converted
   * to group chat successfully.
   *
   * <p>Note that the method is not thread-safe since it's always called from the UI and Android
   * uses single thread mode for UI.
   */
  public void convertToGroupChat(String nickname) {
    if (mIsGroupChat || mConvertingToGroupChat) {
      return;
    }

    mConvertingToGroupChat = true;
    new ChatConvertor().convertToGroupChat(nickname);
  }

  public boolean isGroupChatSession() {
    return mIsGroupChat;
  }

  public String getName() {

    if (isGroupChatSession()) return ((ChatGroup) mChatSession.getParticipant()).getName();
    else return ((Contact) mChatSession.getParticipant()).getName();
  }

  public String getAddress() {
    return mChatSession.getParticipant().getAddress().getAddress();
  }

  public long getId() {
    return ContentUris.parseId(mChatURI);
  }

  public void inviteContact(String contact) {
    if (!mIsGroupChat) {
      return;
    }
    ContactListManagerAdapter listManager =
        (ContactListManagerAdapter) mConnection.getContactListManager();
    Contact invitee = new Contact(new XmppAddress(contact), contact);
    getGroupManager().inviteUserAsync((ChatGroup) mChatSession.getParticipant(), invitee);
  }

  public void leave() {
    if (mIsGroupChat) {
      getGroupManager().leaveChatGroupAsync((ChatGroup) mChatSession.getParticipant());
    }

    mContentResolver.delete(mMessageURI, null, null);
    mContentResolver.delete(mChatURI, null, null);
    mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), getAddress());
    mChatSessionManager.closeChatSession(this);
  }

  public void leaveIfInactive() {
    //    if (mChatSession.getHistoryMessages().isEmpty()) {
    leave();
    //  }
  }

  public boolean sendKnock() {

    return mChatSession.sendKnock(mConnection.getLoginUser().getAddress().getAddress());
  }

  public void sendMessage(String text, boolean isResend) {

    if (mConnection.getState() != ImConnection.LOGGED_IN) {
      // connection has been suspended, save the message without send it
      long now = System.currentTimeMillis();
      insertMessageInDb(null, text, now, Imps.MessageType.POSTPONED);
      return;
    }

    org.awesomeapp.messenger.model.Message msg = new org.awesomeapp.messenger.model.Message(text);
    msg.setID(nextID());

    msg.setFrom(mConnection.getLoginUser().getAddress());
    msg.setType(Imps.MessageType.OUTGOING);

    long sendTime = System.currentTimeMillis();

    if (!isResend) {
      insertMessageInDb(null, text, sendTime, msg.getType(), 0, msg.getID());
      insertOrUpdateChat(text);
    }

    int newType = mChatSession.sendMessageAsync(msg);

    if (msg.getDateTime() != null) sendTime = msg.getDateTime().getTime();

    updateMessageInDb(msg.getID(), newType, sendTime);
  }

  public boolean offerData(String offerId, String url, String type) {
    if (mConnection.getState() == ImConnection.SUSPENDED) {
      // TODO send later
      return false;
    }

    HashMap<String, String> headers = null;
    if (type != null) {
      headers = new HashMap<>();
      headers.put("Mime-Type", type);
    }

    try {
      Address localUser = mConnection.getLoginUser().getAddress();

      if (mChatSession.getParticipant() instanceof Contact) {

        Address remoteUser = new XmppAddress(getDefaultOtrChatSession().getRemoteUserId());
        mDataHandler.offerData(offerId, localUser, remoteUser, url, headers);
      } else if (mChatSession.getParticipant() instanceof ChatGroup) {
        ChatGroup group = (ChatGroup) mChatSession.getParticipant();

        for (Contact member : group.getMembers()) {
          mDataHandler.offerData(offerId, localUser, member.getAddress(), url, headers);
        }
      }

      return true;
    } catch (Exception ioe) {
      Log.w(ImApp.LOG_TAG, "unable to offer data", ioe);
      return false;
    }
  }

  /** Sends a message to other participant(s) in this session without adding it to the history. */
  /*
  public void sendMessageWithoutHistory(String text) {

   Message msg = new Message(text);
   // TODO OTRCHAT use a lower level method
   mChatSession.sendMessageAsync(msg);
  }*/

  boolean hasPostponedMessages() {
    String[] projection =
        new String[] {
          BaseColumns._ID,
          Imps.Messages.BODY,
          Imps.Messages.PACKET_ID,
          Imps.Messages.DATE,
          Imps.Messages.TYPE,
          Imps.Messages.IS_DELIVERED
        };
    String selection = Imps.Messages.TYPE + "=?";

    boolean result = false;

    Cursor c =
        mContentResolver.query(
            mMessageURI,
            projection,
            selection,
            new String[] {Integer.toString(Imps.MessageType.POSTPONED)},
            null);
    if (c == null) {
      RemoteImService.debug("Query error while querying postponed messages");
      return false;
    } else if (c.getCount() > 0) {
      result = true;
    }

    c.close();
    return true;
  }

  synchronized void sendPostponedMessages() {
    String[] projection =
        new String[] {
          BaseColumns._ID,
          Imps.Messages.BODY,
          Imps.Messages.PACKET_ID,
          Imps.Messages.DATE,
          Imps.Messages.TYPE,
          Imps.Messages.IS_DELIVERED
        };
    String selection = Imps.Messages.TYPE + "=?";

    Cursor c =
        mContentResolver.query(
            mMessageURI,
            projection,
            selection,
            new String[] {Integer.toString(Imps.MessageType.POSTPONED)},
            null);
    if (c == null) {
      RemoteImService.debug("Query error while querying postponed messages");
      return;
    }

    ArrayList<String> messages = new ArrayList<String>();

    while (c.moveToNext()) messages.add(c.getString(1));

    c.close();

    removeMessageInDb(Imps.MessageType.POSTPONED);

    for (String body : messages) sendMessage(body, false);
  }

  public void registerChatListener(IChatListener listener) {
    if (listener != null) {
      mRemoteListeners.register(listener);

      if (mDataHandlerListener != null) mDataHandlerListener.checkLastTransferRequest();
    }
  }

  public void unregisterChatListener(IChatListener listener) {
    if (listener != null) {
      mRemoteListeners.unregister(listener);
    }
  }

  public void markAsRead() {
    if (mHasUnreadMessages) {

      /**
       * we want to keep the last message now ContentValues values = new ContentValues(1);
       * values.put(Imps.Chats.LAST_UNREAD_MESSAGE, (String) null);
       * mConnection.getContext().getContentResolver().update(mChatURI, values, null, null);
       */
      String baseUsername = mChatSession.getParticipant().getAddress().getBareAddress();
      mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), baseUsername);

      mHasUnreadMessages = false;
    }
  }

  String getNickName(String username) {
    ImEntity participant = mChatSession.getParticipant();
    if (mIsGroupChat) {

      ChatGroup group = (ChatGroup) participant;
      List<Contact> members = group.getMembers();
      for (Contact c : members) {
        if (username.equals(c.getAddress().getAddress())) {

          return c.getAddress().getResource();
        }
      }

      // not found, impossible
      String[] parts = username.split("/");
      return parts[parts.length - 1];
    } else {
      return ((Contact) participant).getName();
    }
  }

  void onConvertToGroupChatSuccess(ChatGroup group) {
    Contact oldParticipant = (Contact) mChatSession.getParticipant();
    String oldAddress = getAddress();
    //    mChatSession.setParticipant(group);
    mChatSessionManager.updateChatSession(oldAddress, this);

    Uri oldChatUri = mChatURI;
    Uri oldMessageUri = mMessageURI;
    init(group, false);
    // copyHistoryMessages(oldParticipant);

    mContentResolver.delete(oldMessageUri, NON_CHAT_MESSAGE_SELECTION, null);
    mContentResolver.delete(oldChatUri, null, null);

    mListenerAdapter.notifyChatSessionConverted();
    mConvertingToGroupChat = false;
  }

  /**
   * private void copyHistoryMessages(Contact oldParticipant) {
   * List<org.awesomeapp.messenger.model.Message> historyMessages =
   * mChatSession.getHistoryMessages(); int total = historyMessages.size(); int start = total >
   * MAX_HISTORY_COPY_COUNT ? total - MAX_HISTORY_COPY_COUNT : 0; for (int i = start; i < total;
   * i++) { org.awesomeapp.messenger.model.Message msg = historyMessages.get(i); boolean incoming =
   * msg.getFrom().equals(oldParticipant.getAddress()); String contact = incoming ?
   * oldParticipant.getName() : null; long time = msg.getDateTime().getTime();
   * insertMessageInDb(contact, msg.getBody(), time, incoming ? Imps.MessageType.INCOMING :
   * Imps.MessageType.OUTGOING); } }
   */
  void insertOrUpdateChat(String message) {

    ContentValues values = new ContentValues(2);

    values.put(Imps.Chats.LAST_MESSAGE_DATE, System.currentTimeMillis());
    values.put(Imps.Chats.LAST_UNREAD_MESSAGE, message);

    values.put(Imps.Chats.GROUP_CHAT, mIsGroupChat);
    // ImProvider.insert() will replace the chat if it already exist.
    mContentResolver.insert(mChatURI, values);
  }

  private long insertOrUpdateGroupContactInDb(ChatGroup group) {
    // Insert a record in contacts table
    ContentValues values = new ContentValues(4);
    values.put(Imps.Contacts.USERNAME, group.getAddress().getAddress());
    values.put(Imps.Contacts.NICKNAME, group.getName());
    values.put(Imps.Contacts.CONTACTLIST, ContactListManagerAdapter.LOCAL_GROUP_LIST_ID);
    values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_GROUP);

    Uri contactUri =
        ContentUris.withAppendedId(
            ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, mConnection.mProviderId),
            mConnection.mAccountId);

    ContactListManagerAdapter listManager =
        (ContactListManagerAdapter) mConnection.getContactListManager();

    long id = listManager.queryGroup(group);

    if (id == -1) {
      id = ContentUris.parseId(mContentResolver.insert(contactUri, values));

      ArrayList<ContentValues> memberValues = new ArrayList<ContentValues>();
      Contact self = mConnection.getLoginUser();
      for (Contact member : group.getMembers()) {
        if (!member.equals(self)) { // avoid to insert the user himself
          ContentValues memberValue = new ContentValues(2);
          memberValue.put(Imps.GroupMembers.USERNAME, member.getAddress().getAddress());
          memberValue.put(Imps.GroupMembers.NICKNAME, member.getName());
          memberValues.add(memberValue);
        }
      }
      if (!memberValues.isEmpty()) {
        ContentValues[] result = new ContentValues[memberValues.size()];
        memberValues.toArray(result);
        Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, id);
        mContentResolver.bulkInsert(memberUri, result);
      }
    }

    return id;
  }

  void insertGroupMemberInDb(Contact member) {

    if (mChatURI != null) {
      ContentValues values1 = new ContentValues(2);
      values1.put(Imps.GroupMembers.USERNAME, member.getAddress().getAddress());
      values1.put(Imps.GroupMembers.NICKNAME, member.getName());
      ContentValues values = values1;

      long groupId = ContentUris.parseId(mChatURI);
      Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId);
      mContentResolver.insert(uri, values);

      //  insertMessageInDb(member.getName(), null, System.currentTimeMillis(),
      //      Imps.MessageType.PRESENCE_AVAILABLE);
    }
  }

  void deleteGroupMemberInDb(Contact member) {
    String where = Imps.GroupMembers.USERNAME + "=?";
    String[] selectionArgs = {member.getAddress().getAddress()};

    if (mChatURI != null) {
      long groupId = ContentUris.parseId(mChatURI);
      Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId);
      mContentResolver.delete(uri, where, selectionArgs);
    }
    //  insertMessageInDb(member.getName(), null, System.currentTimeMillis(),
    //    Imps.MessageType.PRESENCE_UNAVAILABLE);
  }

  void insertPresenceUpdatesMsg(String contact, Presence presence) {
    int status = presence.getStatus();

    Integer previousStatus = mContactStatusMap.get(contact);
    if (previousStatus != null && previousStatus == status) {
      // don't insert the presence message if it's the same status
      // with the previous presence update notification
      return;
    }

    mContactStatusMap.put(contact, status);
    int messageType;
    switch (status) {
      case Presence.AVAILABLE:
        messageType = Imps.MessageType.PRESENCE_AVAILABLE;
        break;

      case Presence.AWAY:
      case Presence.IDLE:
        messageType = Imps.MessageType.PRESENCE_AWAY;
        break;

      case Presence.DO_NOT_DISTURB:
        messageType = Imps.MessageType.PRESENCE_DND;
        break;

      default:
        messageType = Imps.MessageType.PRESENCE_UNAVAILABLE;
        break;
    }

    if (mIsGroupChat) {
      insertMessageInDb(contact, null, System.currentTimeMillis(), messageType);
    } else {
      insertMessageInDb(null, null, System.currentTimeMillis(), messageType);
    }
  }

  void removeMessageInDb(int type) {
    mContentResolver.delete(
        mMessageURI, Imps.Messages.TYPE + "=?", new String[] {Integer.toString(type)});
  }

  Uri insertMessageInDb(String contact, String body, long time, int type) {
    return insertMessageInDb(contact, body, time, type, 0 /*No error*/, nextID());
  }

  /** A prefix helps to make sure that ID's are unique across mutliple instances. */
  private static String prefix = StringUtils.randomString(5) + "-";

  /** Keeps track of the current increment, which is appended to the prefix to forum a unique ID. */
  private static long id = 0;

  static String nextID() {
    return prefix + Long.toString(id++);
  }

  Uri insertMessageInDb(String contact, String body, long time, int type, int errCode, String id) {
    boolean isEncrypted = true;
    try {
      isEncrypted = getDefaultOtrChatSession().isChatEncrypted();
    } catch (RemoteException e) {
      // Leave it as encrypted so it gets stored in memory
      // FIXME(miron)
    }
    return Imps.insertMessageInDb(
        mContentResolver,
        mIsGroupChat,
        mContactId,
        isEncrypted,
        contact,
        body,
        time,
        type,
        errCode,
        id,
        null);
  }

  int updateMessageInDb(String id, int type, long time) {

    Uri.Builder builder = Imps.Messages.OTR_MESSAGES_CONTENT_URI_BY_PACKET_ID.buildUpon();
    builder.appendPath(id);

    ContentValues values = new ContentValues(2);
    values.put(Imps.Messages.TYPE, type);
    values.put(Imps.Messages.THREAD_ID, mContactId);
    if (time != -1) values.put(Imps.Messages.DATE, time);

    int result = mContentResolver.update(builder.build(), values, null, null);

    if (result == 0) {
      builder = Imps.Messages.CONTENT_URI_MESSAGES_BY_PACKET_ID.buildUpon();
      builder.appendPath(id);
      result = mContentResolver.update(builder.build(), values, null, null);
    }

    return result;
  }

  class ListenerAdapter implements MessageListener, GroupMemberListener, OtrEngineListener {

    public synchronized boolean onIncomingMessage(
        ChatSession ses, final org.awesomeapp.messenger.model.Message msg) {
      String body = msg.getBody();
      String username = msg.getFrom().getAddress();
      String bareUsername = msg.getFrom().getBareAddress();
      String nickname = getNickName(username);
      long time = msg.getDateTime().getTime();

      if (msg.getID() != null && Imps.messageExists(mContentResolver, msg.getID())) {
        return false; // this message is a duplicate
      }

      insertOrUpdateChat(body);

      boolean wasMessageSeen = false;

      if (msg.getID() == null) insertMessageInDb(nickname, body, time, msg.getType());
      else insertMessageInDb(nickname, body, time, msg.getType(), 0, msg.getID());

      int N = mRemoteListeners.beginBroadcast();
      for (int i = 0; i < N; i++) {
        IChatListener listener = mRemoteListeners.getBroadcastItem(i);
        try {
          boolean wasSeen = listener.onIncomingMessage(ChatSessionAdapter.this, msg);

          if (wasSeen) wasMessageSeen = wasSeen;

        } catch (RemoteException e) {
          // The RemoteCallbackList will take care of removing the
          // dead listeners.
        }
      }
      mRemoteListeners.finishBroadcast();

      // Due to the move to fragments, we could have listeners for ChatViews that are not visible on
      // the screen.
      // This is for fragments adjacent to the current one.  Therefore we can't use the existence of
      // listeners
      // as a filter on notifications.
      if (!wasMessageSeen) {
        // reinstated body display here in the notification; perhaps add preferences to turn that
        // off
        mStatusBarNotifier.notifyChat(
            mConnection.getProviderId(),
            mConnection.getAccountId(),
            getId(),
            bareUsername,
            nickname,
            body,
            false);
      }

      mHasUnreadMessages = true;
      return true;
    }

    public void onSendMessageError(
        ChatSession ses,
        final org.awesomeapp.messenger.model.Message msg,
        final ImErrorInfo error) {
      insertMessageInDb(
          null, null, System.currentTimeMillis(), Imps.MessageType.OUTGOING, error.getCode(), null);

      final int N = mRemoteListeners.beginBroadcast();
      for (int i = 0; i < N; i++) {
        IChatListener listener = mRemoteListeners.getBroadcastItem(i);
        try {
          listener.onSendMessageError(ChatSessionAdapter.this, msg, error);
        } catch (RemoteException e) {
          // The RemoteCallbackList will take care of removing the
          // dead listeners.
        }
      }
      mRemoteListeners.finishBroadcast();
    }

    public void onSubjectChanged(ChatGroup group, String subject) {
      if (mChatURI != null) {
        ContentValues values1 = new ContentValues(1);
        values1.put(Imps.Contacts.NICKNAME, subject);
        ContentValues values = values1;

        Uri uriContact = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, mContactId);
        mContentResolver.update(uriContact, values, null, null);

        //  insertMessageInDb(member.getName(), null, System.currentTimeMillis(),
        //      Imps.MessageType.PRESENCE_AVAILABLE);
      }
    }

    public void onMemberJoined(ChatGroup group, final Contact contact) {
      insertGroupMemberInDb(contact);

      final int N = mRemoteListeners.beginBroadcast();
      for (int i = 0; i < N; i++) {
        IChatListener listener = mRemoteListeners.getBroadcastItem(i);
        try {
          listener.onContactJoined(ChatSessionAdapter.this, contact);
        } catch (RemoteException e) {
          // The RemoteCallbackList will take care of removing the
          // dead listeners.
        }
      }
      mRemoteListeners.finishBroadcast();
    }

    public void onMemberLeft(ChatGroup group, final Contact contact) {
      deleteGroupMemberInDb(contact);

      final int N = mRemoteListeners.beginBroadcast();
      for (int i = 0; i < N; i++) {
        IChatListener listener = mRemoteListeners.getBroadcastItem(i);
        try {
          listener.onContactLeft(ChatSessionAdapter.this, contact);
        } catch (RemoteException e) {
          // The RemoteCallbackList will take care of removing the
          // dead listeners.
        }
      }
      mRemoteListeners.finishBroadcast();
    }

    public void onError(ChatGroup group, final ImErrorInfo error) {
      // TODO: insert an error message?
      final int N = mRemoteListeners.beginBroadcast();
      for (int i = 0; i < N; i++) {
        IChatListener listener = mRemoteListeners.getBroadcastItem(i);
        try {
          listener.onInviteError(ChatSessionAdapter.this, error);
        } catch (RemoteException e) {
          // The RemoteCallbackList will take care of removing the
          // dead listeners.
        }
      }
      mRemoteListeners.finishBroadcast();
    }

    public void notifyChatSessionConverted() {
      final int N = mRemoteListeners.beginBroadcast();
      for (int i = 0; i < N; i++) {
        IChatListener listener = mRemoteListeners.getBroadcastItem(i);
        try {
          listener.onConvertedToGroupChat(ChatSessionAdapter.this);
        } catch (RemoteException e) {
          // The RemoteCallbackList will take care of removing the
          // dead listeners.
        }
      }
      mRemoteListeners.finishBroadcast();
    }

    @Override
    public void onIncomingReceipt(ChatSession ses, String id) {
      Imps.updateConfirmInDb(mContentResolver, mContactId, id, true);

      synchronized (mRemoteListeners) {
        int N = mRemoteListeners.beginBroadcast();
        for (int i = 0; i < N; i++) {
          IChatListener listener = mRemoteListeners.getBroadcastItem(i);
          try {
            listener.onIncomingReceipt(ChatSessionAdapter.this, id);
          } catch (RemoteException e) {
            // The RemoteCallbackList will take care of removing the
            // dead listeners.
          }
        }
        mRemoteListeners.finishBroadcast();
      }
    }

    @Override
    public void onMessagePostponed(ChatSession ses, String id) {
      updateMessageInDb(id, Imps.MessageType.POSTPONED, -1);
    }

    @Override
    public void onReceiptsExpected(ChatSession ses, boolean isExpected) {
      // TODO

    }

    @Override
    public void sessionStatusChanged(SessionID sessionID) {

      if (sessionID
          .getRemoteUserId()
          .equals(mChatSession.getParticipant().getAddress().getAddress()))
        onStatusChanged(mChatSession, OtrChatManager.getInstance().getSessionStatus(sessionID));
    }

    @Override
    public void onStatusChanged(ChatSession session, SessionStatus status) {
      final int N = mRemoteListeners.beginBroadcast();
      for (int i = 0; i < N; i++) {
        IChatListener listener = mRemoteListeners.getBroadcastItem(i);
        try {
          listener.onStatusChanged(ChatSessionAdapter.this);
        } catch (RemoteException e) {
          // The RemoteCallbackList will take care of removing the
          // dead listeners.   // TODO Auto-generated method stub
        }
      }
      mRemoteListeners.finishBroadcast();
      mDataHandler.onOtrStatusChanged(status);

      if (status == SessionStatus.ENCRYPTED) {
        sendPostponedMessages();
      }

      mLastSessionStatus = status;
    }

    @Override
    public void onIncomingDataRequest(
        ChatSession session, org.awesomeapp.messenger.model.Message msg, byte[] value) {
      mDataHandler.onIncomingRequest(msg.getFrom(), msg.getTo(), value);
    }

    @Override
    public void onIncomingDataResponse(
        ChatSession session, org.awesomeapp.messenger.model.Message msg, byte[] value) {
      mDataHandler.onIncomingResponse(msg.getFrom(), msg.getTo(), value);
    }

    @Override
    public void onIncomingTransferRequest(final Transfer transfer) {}
  }

  class ChatConvertor implements GroupListener, GroupMemberListener {
    private ChatGroupManager mGroupMgr;
    private String mGroupName;

    public ChatConvertor() {
      mGroupMgr = mConnection.mGroupManager;
    }

    public void convertToGroupChat(String nickname) {
      mGroupMgr.addGroupListener(this);
      mGroupName = "G" + System.currentTimeMillis();
      try {
        mGroupMgr.createChatGroupAsync(mGroupName, nickname, nickname);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    public void onGroupCreated(ChatGroup group) {
      if (mGroupName.equalsIgnoreCase(group.getName())) {
        mGroupMgr.removeGroupListener(this);
        group.addMemberListener(this);
        mGroupMgr.inviteUserAsync(group, (Contact) mChatSession.getParticipant());
      }
    }

    public void onMemberJoined(ChatGroup group, Contact contact) {
      if (mChatSession.getParticipant().equals(contact)) {
        onConvertToGroupChatSuccess(group);
      }

      mContactStatusMap.put(contact.getName(), contact.getPresence().getStatus());
    }

    public void onSubjectChanged(ChatGroup group, String subject) {}

    public void onGroupDeleted(ChatGroup group) {}

    public void onGroupError(int errorType, String groupName, ImErrorInfo error) {}

    public void onJoinedGroup(ChatGroup group) {}

    public void onLeftGroup(ChatGroup group) {}

    public void onError(ChatGroup group, ImErrorInfo error) {}

    public void onMemberLeft(ChatGroup group, Contact contact) {
      mContactStatusMap.remove(contact.getName());
    }
  }

  @Override
  public void setDataListener(IDataListener dataListener) throws RemoteException {

    mDataListener = dataListener;
    mDataHandler.setDataListener(mDataListener);
  }

  @Override
  public void setIncomingFileResponse(String transferForm, boolean acceptThis, boolean acceptAll) {

    mAcceptTransfer = acceptThis;
    mAcceptAllTransfer = acceptAll;
    mWaitingForResponse = false;

    mDataHandler.acceptTransfer(mLastFileUrl, transferForm);
  }

  class DataHandlerListenerImpl extends IDataListener.Stub {

    @Override
    public void onTransferComplete(
        boolean outgoing,
        String offerId,
        String from,
        String url,
        String mimeType,
        String filePath) {

      try {

        if (outgoing) {
          Imps.updateConfirmInDb(service.getContentResolver(), mContactId, offerId, true);
        } else {

          try {
            boolean isVerified = getDefaultOtrChatSession().isKeyVerified(from);

            int type =
                isVerified
                    ? Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED
                    : Imps.MessageType.INCOMING_ENCRYPTED;

            insertOrUpdateChat(filePath);

            Uri messageUri =
                Imps.insertMessageInDb(
                    service.getContentResolver(),
                    mIsGroupChat,
                    getId(),
                    true,
                    from,
                    filePath,
                    System.currentTimeMillis(),
                    type,
                    0,
                    offerId,
                    mimeType);

            int percent = (int) (100);

            String[] path = url.split("/");
            String sanitizedPath = SystemServices.sanitize(path[path.length - 1]);

            final int N = mRemoteListeners.beginBroadcast();
            for (int i = 0; i < N; i++) {
              IChatListener listener = mRemoteListeners.getBroadcastItem(i);
              try {
                listener.onIncomingFileTransferProgress(sanitizedPath, percent);
              } catch (RemoteException e) {
                // The RemoteCallbackList will take care of removing the
                // dead listeners.
              }
            }
            mRemoteListeners.finishBroadcast();

            if (N == 0) {
              String nickname = getNickName(from);

              mStatusBarNotifier.notifyChat(
                  mConnection.getProviderId(),
                  mConnection.getAccountId(),
                  getId(),
                  from,
                  nickname,
                  service.getString(R.string.file_notify_text, mimeType, nickname),
                  false);
            }
          } catch (Exception e) {
            Log.e(ImApp.LOG_TAG, "Error updating file transfer progress", e);
          }
        }

        /**
         * if (mimeType != null && mimeType.startsWith("audio")) { MediaPlayer mp = new
         * MediaPlayer(); try { mp.setDataSource(file.getCanonicalPath());
         *
         * <p>mp.prepare(); mp.start();
         *
         * <p>} catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); }
         * }
         */
      } catch (Exception e) {
        //   mHandler.showAlert(service.getString(R.string.error_chat_file_transfer_title),
        // service.getString(R.string.error_chat_file_transfer_body));
        OtrDebugLogger.log("error reading file", e);
      }
    }

    @Override
    public synchronized void onTransferFailed(
        boolean outgoing, String offerId, String from, String url, String reason) {

      String[] path = url.split("/");
      String sanitizedPath = SystemServices.sanitize(path[path.length - 1]);

      final int N = mRemoteListeners.beginBroadcast();
      for (int i = 0; i < N; i++) {
        IChatListener listener = mRemoteListeners.getBroadcastItem(i);
        try {
          listener.onIncomingFileTransferError(sanitizedPath, reason);
        } catch (RemoteException e) {
          // The RemoteCallbackList will take care of removing the
          // dead listeners.
        }
      }
      mRemoteListeners.finishBroadcast();
    }

    @Override
    public synchronized void onTransferProgress(
        boolean outgoing, String offerId, String from, String url, float percentF) {

      int percent = (int) (100 * percentF);

      String[] path = url.split("/");
      String sanitizedPath = SystemServices.sanitize(path[path.length - 1]);

      try {
        final int N = mRemoteListeners.beginBroadcast();
        for (int i = 0; i < N; i++) {
          IChatListener listener = mRemoteListeners.getBroadcastItem(i);
          try {
            listener.onIncomingFileTransferProgress(sanitizedPath, percent);
          } catch (RemoteException e) {
            // The RemoteCallbackList will take care of removing the
            // dead listeners.
          }
        }
      } catch (Exception e) {
        Log.e(ImApp.LOG_TAG, "error broadcasting progress", e);
      } finally {
        mRemoteListeners.finishBroadcast();
      }
    }

    private String mLastTransferFrom;
    private String mLastTransferUrl;

    public void checkLastTransferRequest() {
      if (mLastTransferFrom != null) {
        onTransferRequested(
            mLastTransferUrl, mLastTransferFrom, mLastTransferFrom, mLastTransferUrl);
        mLastTransferFrom = null;
        mLastTransferUrl = null;
      }
    }

    @Override
    public synchronized boolean onTransferRequested(
        String offerId, String from, String to, String transferUrl) {

      mAcceptTransfer = false;
      mWaitingForResponse = true;
      mLastFileUrl = transferUrl;

      if (mAcceptAllTransfer) {
        mAcceptTransfer = true;
        mWaitingForResponse = false;
        mLastTransferFrom = from;
        mLastTransferUrl = transferUrl;

        mDataHandler.acceptTransfer(mLastFileUrl, from);
      } else {
        try {
          final int N = mRemoteListeners.beginBroadcast();

          if (N > 0) {
            for (int i = 0; i < N; i++) {
              IChatListener listener = mRemoteListeners.getBroadcastItem(i);
              try {
                listener.onIncomingFileTransfer(from, transferUrl);
              } catch (RemoteException e) {
                // The RemoteCallbackList will take care of removing the
                // dead listeners.
              }
            }
          } else {
            mLastTransferFrom = from;
            mLastTransferUrl = transferUrl;
            String nickname = getNickName(from);

            // reinstated body display here in the notification; perhaps add preferences to turn
            // that off
            mStatusBarNotifier.notifyChat(
                mConnection.getProviderId(),
                mConnection.getAccountId(),
                getId(),
                from,
                nickname,
                "Incoming file request",
                false);
          }
        } finally {
          mRemoteListeners.finishBroadcast();
        }

        mAcceptTransfer = false; // for now, wait for the user callback
      }

      return mAcceptTransfer;
    }
  }

  public boolean sendPushWhitelistToken(@NonNull String token) {
    if (mConnection.getState() == ImConnection.SUSPENDED) {
      // TODO Is it possible to postpone a TLV message? e.g: insertMessageInDb with type POSTPONED
      return false;
    }

    // Whitelist tokens are intended for one recipient, for now
    if (isGroupChatSession()) return false;

    org.awesomeapp.messenger.model.Message msg = new org.awesomeapp.messenger.model.Message("");

    msg.setFrom(mConnection.getLoginUser().getAddress());
    msg.setType(Imps.MessageType.OUTGOING);

    mChatSession.sendPushWhitelistTokenAsync(msg, new String[] {token});
    return true;
  }

  public void setContactTyping(Contact contact, boolean isTyping) {

    int N = mRemoteListeners.beginBroadcast();
    for (int i = 0; i < N; i++) {
      IChatListener listener = mRemoteListeners.getBroadcastItem(i);
      try {
        listener.onContactTyping(ChatSessionAdapter.this, contact, isTyping);
      } catch (RemoteException e) {
        // The RemoteCallbackList will take care of removing the
        // dead listeners.
      }
    }
    mRemoteListeners.finishBroadcast();
  }

  public void sendTypingStatus(boolean isTyping) {
    // mConnection.sendTypingStatus("fpp", isTyping);
  }
}
Ejemplo n.º 5
0
/**
 * Handles XMPP transport session.
 *
 * @author Daniel Henninger
 * @author Mehmet Ecevit
 */
public class XMPPSession extends TransportSession<XMPPBuddy> {

  static Logger Log = Logger.getLogger(XMPPSession.class);

  /**
   * Create an XMPP Session instance.
   *
   * @param registration Registration information used for logging in.
   * @param jid JID associated with this session.
   * @param transport Transport instance associated with this session.
   * @param priority Priority of this session.
   */
  public XMPPSession(
      Registration registration, JID jid, XMPPTransport transport, Integer priority) {
    super(registration, jid, transport, priority);
    setSupportedFeature(SupportedFeature.attention);
    setSupportedFeature(SupportedFeature.chatstates);

    Log.debug(
        "Creating " + getTransport().getType() + " session for " + registration.getUsername());
    String connecthost;
    Integer connectport;
    String domain;

    connecthost =
        JiveGlobals.getProperty(
            "plugin.gateway." + getTransport().getType() + ".connecthost",
            (getTransport().getType().equals(TransportType.gtalk)
                ? "talk.google.com"
                : getTransport().getType().equals(TransportType.facebook)
                    ? "chat.facebook.com"
                    : "jabber.org"));
    connectport =
        JiveGlobals.getIntProperty(
            "plugin.gateway." + getTransport().getType() + ".connectport", 5222);

    if (getTransport().getType().equals(TransportType.gtalk)) {
      domain = "gmail.com";
    } else if (getTransport().getType().equals(TransportType.facebook)) {
      // if (connecthost.equals("www.facebook.com")) {
      connecthost = "chat.facebook.com";
      // }
      // if (connectport.equals(80)) {
      connectport = 5222;
      // }
      domain = "chat.facebook.com";
    } else if (getTransport().getType().equals(TransportType.renren)) {
      connecthost = "talk.renren.com";
      connectport = 5222;
      domain = "renren.com";
    } else {
      domain = connecthost;
    }

    // For different domains other than 'gmail.com', which is given with Google Application services
    if (registration.getUsername().indexOf("@") > -1) {
      domain = registration.getUsername().substring(registration.getUsername().indexOf("@") + 1);
    }

    // If administrator specified "*" for domain, allow user to connect to anything.
    if (connecthost.equals("*")) {
      connecthost = domain;
    }

    config = new ConnectionConfiguration(connecthost, connectport, domain);
    config.setCompressionEnabled(
        JiveGlobals.getBooleanProperty(
            "plugin.gateway." + getTransport().getType() + ".usecompression", false));

    if (getTransport().getType().equals(TransportType.facebook)) {
      // SASLAuthentication.supportSASLMechanism("PLAIN", 0);
      // config.setSASLAuthenticationEnabled(false);
      // config.setSecurityMode(ConnectionConfiguration.SecurityMode.enabled);
    }

    // instead, send the initial presence right after logging in. This
    // allows us to use a different presence mode than the plain old
    // 'available' as initial presence.
    config.setSendPresence(false);

    if (getTransport().getType().equals(TransportType.gtalk)
        && JiveGlobals.getBooleanProperty("plugin.gateway.gtalk.mailnotifications", true)) {
      ProviderManager.getInstance()
          .addIQProvider(
              GoogleMailBoxPacket.MAILBOX_ELEMENT,
              GoogleMailBoxPacket.MAILBOX_NAMESPACE,
              new GoogleMailBoxPacket.Provider());
      ProviderManager.getInstance()
          .addExtensionProvider(
              GoogleNewMailExtension.ELEMENT_NAME,
              GoogleNewMailExtension.NAMESPACE,
              new GoogleNewMailExtension.Provider());
    }
  }

  /*
   * XMPP connection
   */
  public XMPPConnection conn = null;

  /** XMPP listener */
  private XMPPListener listener = null;

  /** Run thread. */
  private Thread runThread = null;

  /** Instance that will handle all presence stanzas sent from the legacy domain */
  private XMPPPresenceHandler presenceHandler = null;

  /*
   * XMPP connection configuration
   */
  private final ConnectionConfiguration config;

  /** Timer to check for online status. */
  public Timer timer = new Timer();

  /** Interval at which status is checked. */
  private int timerInterval = 60000; // 1 minute

  /** Mail checker */
  MailCheck mailCheck;

  /** XMPP Resource - the resource we are using (randomly generated) */
  public String xmppResource = StringUtils.randomString(10);

  /**
   * Returns a full JID based off of a username passed in.
   *
   * <p>If it already looks like a JID, returns what was passed in.
   *
   * @param username Username to turn into a JID.
   * @return Converted username.
   */
  public String generateFullJID(String username) {
    if (username.indexOf("@") > -1) {
      return username;
    }

    if (getTransport().getType().equals(TransportType.gtalk)) {
      return username + "@" + "gmail.com";
    } else if (getTransport().getType().equals(TransportType.facebook)) {
      return username + "@" + "chat.facebook.com";
    } else if (getTransport().getType().equals(TransportType.renren)) {
      return username + "@" + "renren.com";
    } else if (getTransport().getType().equals(TransportType.livejournal)) {
      return username + "@" + "livejournal.com";
    } else {
      String connecthost =
          JiveGlobals.getProperty(
              "plugin.gateway." + getTransport().getType() + ".connecthost",
              (getTransport().getType().equals(TransportType.gtalk)
                  ? "talk.google.com"
                  : getTransport().getType().equals(TransportType.facebook)
                      ? "chat.facebook.com"
                      : "jabber.org"));
      return username + "@" + connecthost;
    }
  }

  /**
   * Returns a username based off of a registered name (possible JID) passed in.
   *
   * <p>If it already looks like a username, returns what was passed in.
   *
   * @param regName Registered name to turn into a username.
   * @return Converted registered name.
   */
  public String generateUsername(String regName) {
    if (regName.equals("{PLATFORM}")) {
      return JiveGlobals.getProperty("plugin.gateway.facebook.platform.apikey")
          + "|"
          + JiveGlobals.getProperty("plugin.gateway.facebook.platform.apisecret");
    } else if (regName.indexOf("@") > -1) {
      if (getTransport().getType().equals(TransportType.gtalk)) {
        return regName;
      } else {
        return regName.substring(0, regName.indexOf("@"));
      }
    } else {
      if (getTransport().getType().equals(TransportType.gtalk)) {
        return regName + "@gmail.com";
      } else {
        return regName;
      }
    }
  }

  /** @see net.sf.kraken.session.TransportSession#logIn(net.sf.kraken.type.PresenceType, String) */
  @Override
  public void logIn(PresenceType presenceType, String verboseStatus) {
    final org.jivesoftware.smack.packet.Presence presence =
        new org.jivesoftware.smack.packet.Presence(
            org.jivesoftware.smack.packet.Presence.Type.available);
    if (JiveGlobals.getBooleanProperty(
            "plugin.gateway." + getTransport().getType() + ".avatars", true)
        && getAvatar() != null) {
      Avatar avatar = getAvatar();
      // Same thing in this case, so lets go ahead and set them.
      avatar.setLegacyIdentifier(avatar.getXmppHash());
      VCardUpdateExtension ext = new VCardUpdateExtension();
      ext.setPhotoHash(avatar.getLegacyIdentifier());
      presence.addExtension(ext);
    }
    final Presence.Mode pMode =
        ((XMPPTransport) getTransport()).convertGatewayStatusToXMPP(presenceType);
    if (pMode != null) {
      presence.setMode(pMode);
    }
    if (verboseStatus != null && verboseStatus.trim().length() > 0) {
      presence.setStatus(verboseStatus);
    }
    setPendingPresenceAndStatus(presenceType, verboseStatus);

    if (!this.isLoggedIn()) {
      listener = new XMPPListener(this);
      presenceHandler = new XMPPPresenceHandler(this);
      runThread =
          new Thread() {
            @Override
            public void run() {
              String userName = generateUsername(registration.getUsername());
              conn = new XMPPConnection(config);
              try {
                conn.getSASLAuthentication()
                    .registerSASLMechanism("DIGEST-MD5", MySASLDigestMD5Mechanism.class);
                if (getTransport().getType().equals(TransportType.facebook)
                    && registration.getUsername().equals("{PLATFORM}")) {
                  conn.getSASLAuthentication()
                      .registerSASLMechanism(
                          "X-FACEBOOK-PLATFORM", FacebookConnectSASLMechanism.class);
                  conn.getSASLAuthentication().supportSASLMechanism("X-FACEBOOK-PLATFORM", 0);
                }

                Roster.setDefaultSubscriptionMode(SubscriptionMode.manual);
                conn.connect();
                conn.addConnectionListener(listener);
                try {
                  conn.addPacketListener(
                      presenceHandler,
                      new PacketTypeFilter(org.jivesoftware.smack.packet.Presence.class));
                  // Use this to filter out anything we don't care about
                  conn.addPacketListener(
                      listener,
                      new OrFilter(
                          new PacketTypeFilter(GoogleMailBoxPacket.class),
                          new PacketExtensionFilter(
                              GoogleNewMailExtension.ELEMENT_NAME,
                              GoogleNewMailExtension.NAMESPACE)));
                  conn.login(userName, registration.getPassword(), xmppResource);
                  conn.sendPacket(presence); // send initial presence.
                  conn.getChatManager().addChatListener(listener);
                  conn.getRoster().addRosterListener(listener);

                  if (JiveGlobals.getBooleanProperty(
                          "plugin.gateway." + getTransport().getType() + ".avatars",
                          !TransportType.facebook.equals(getTransport().getType()))
                      && getAvatar() != null) {
                    new Thread() {
                      @Override
                      public void run() {
                        Avatar avatar = getAvatar();

                        VCard vCard = new VCard();
                        try {
                          vCard.load(conn);
                          vCard.setAvatar(
                              Base64.decode(avatar.getImageData()), avatar.getMimeType());
                          vCard.save(conn);
                        } catch (XMPPException e) {
                          Log.debug("XMPP: Error while updating vcard for avatar change.", e);
                        } catch (NotFoundException e) {
                          Log.debug("XMPP: Unable to find avatar while setting initial.", e);
                        }
                      }
                    }.start();
                  }

                  setLoginStatus(TransportLoginStatus.LOGGED_IN);
                  syncUsers();

                  if (getTransport().getType().equals(TransportType.gtalk)
                      && JiveGlobals.getBooleanProperty(
                          "plugin.gateway.gtalk.mailnotifications", true)) {
                    conn.sendPacket(
                        new IQWithPacketExtension(
                            generateFullJID(getRegistration().getUsername()),
                            new GoogleUserSettingExtension(null, true, null),
                            IQ.Type.SET));
                    conn.sendPacket(
                        new IQWithPacketExtension(
                            generateFullJID(getRegistration().getUsername()),
                            new GoogleMailNotifyExtension()));
                    mailCheck = new MailCheck();
                    timer.schedule(mailCheck, timerInterval, timerInterval);
                  }
                } catch (XMPPException e) {
                  Log.debug(
                      getTransport().getType()
                          + " user's login/password does not appear to be correct: "
                          + getRegistration().getUsername(),
                      e);
                  setFailureStatus(ConnectionFailureReason.USERNAME_OR_PASSWORD_INCORRECT);
                  sessionDisconnectedNoReconnect(
                      LocaleUtils.getLocalizedString("gateway.xmpp.passwordincorrect", "kraken"));
                }
              } catch (XMPPException e) {
                Log.debug(
                    getTransport().getType()
                        + " user is not able to connect: "
                        + getRegistration().getUsername(),
                    e);
                setFailureStatus(ConnectionFailureReason.CAN_NOT_CONNECT);
                sessionDisconnected(
                    LocaleUtils.getLocalizedString("gateway.xmpp.connectionfailed", "kraken"));
              }
            }
          };
      runThread.start();
    }
  }

  /** @see net.sf.kraken.session.TransportSession#logOut() */
  @Override
  public void logOut() {
    cleanUp();
    sessionDisconnectedNoReconnect(null);
  }

  /** @see net.sf.kraken.session.TransportSession#cleanUp() */
  @Override
  public void cleanUp() {
    if (timer != null) {
      try {
        timer.cancel();
      } catch (Exception e) {
        // Ignore
      }
      timer = null;
    }
    if (mailCheck != null) {
      try {
        mailCheck.cancel();
      } catch (Exception e) {
        // Ignore
      }
      mailCheck = null;
    }
    if (conn != null) {
      try {
        conn.removeConnectionListener(listener);
      } catch (Exception e) {
        // Ignore
      }

      try {
        conn.removePacketListener(listener);
      } catch (Exception e) {
        // Ignore
      }
      try {
        conn.removePacketListener(presenceHandler);
      } catch (Exception e) {
        // Ignore
      }
      try {
        conn.getChatManager().removeChatListener(listener);
      } catch (Exception e) {
        // Ignore
      }
      try {
        conn.getRoster().removeRosterListener(listener);
      } catch (Exception e) {
        // Ignore
      }
      try {
        conn.disconnect();
      } catch (Exception e) {
        // Ignore
      }
    }
    conn = null;
    listener = null;
    presenceHandler = null;
    if (runThread != null) {
      try {
        runThread.interrupt();
      } catch (Exception e) {
        // Ignore
      }
      runThread = null;
    }
  }

  /**
   * @see net.sf.kraken.session.TransportSession#updateStatus(net.sf.kraken.type.PresenceType,
   *     String)
   */
  @Override
  public void updateStatus(PresenceType presenceType, String verboseStatus) {
    setPresenceAndStatus(presenceType, verboseStatus);
    final org.jivesoftware.smack.packet.Presence presence = constructCurrentLegacyPresencePacket();

    try {
      conn.sendPacket(presence);
    } catch (IllegalStateException e) {
      Log.debug("XMPP: Not connected while trying to change status.");
    }
  }

  /**
   * @see net.sf.kraken.session.TransportSession#addContact(org.xmpp.packet.JID, String,
   *     java.util.ArrayList)
   */
  @Override
  public void addContact(JID jid, String nickname, ArrayList<String> groups) {
    String mail = getTransport().convertJIDToID(jid);
    try {
      conn.getRoster().createEntry(mail, nickname, groups.toArray(new String[groups.size()]));
      RosterEntry entry = conn.getRoster().getEntry(mail);

      getBuddyManager()
          .storeBuddy(new XMPPBuddy(getBuddyManager(), mail, nickname, entry.getGroups(), entry));
    } catch (XMPPException ex) {
      Log.debug("XMPP: unable to add:" + mail);
    }
  }

  /**
   * @see net.sf.kraken.session.TransportSession#removeContact(net.sf.kraken.roster.TransportBuddy)
   */
  @Override
  public void removeContact(XMPPBuddy contact) {
    RosterEntry user2remove;
    String mail = getTransport().convertJIDToID(contact.getJID());
    user2remove = conn.getRoster().getEntry(mail);
    try {
      conn.getRoster().removeEntry(user2remove);
    } catch (XMPPException ex) {
      Log.debug("XMPP: unable to remove:" + mail);
    }
  }

  /**
   * @see net.sf.kraken.session.TransportSession#updateContact(net.sf.kraken.roster.TransportBuddy)
   */
  @Override
  public void updateContact(XMPPBuddy contact) {
    RosterEntry user2Update;
    String mail = getTransport().convertJIDToID(contact.getJID());
    user2Update = conn.getRoster().getEntry(mail);
    user2Update.setName(contact.getNickname());
    Collection<String> newgroups = contact.getGroups();
    if (newgroups == null) {
      newgroups = new ArrayList<String>();
    }
    for (RosterGroup group : conn.getRoster().getGroups()) {
      if (newgroups.contains(group.getName())) {
        if (!group.contains(user2Update)) {
          try {
            group.addEntry(user2Update);
          } catch (XMPPException e) {
            Log.debug("XMPP: Unable to add roster item to group.");
          }
        }
        newgroups.remove(group.getName());
      } else {
        if (group.contains(user2Update)) {
          try {
            group.removeEntry(user2Update);
          } catch (XMPPException e) {
            Log.debug("XMPP: Unable to delete roster item from group.");
          }
        }
      }
    }
    for (String group : newgroups) {
      RosterGroup newgroup = conn.getRoster().createGroup(group);
      try {
        newgroup.addEntry(user2Update);
      } catch (XMPPException e) {
        Log.debug("XMPP: Unable to add roster item to new group.");
      }
    }
  }

  /** @see net.sf.kraken.session.TransportSession#acceptAddContact(JID) */
  @Override
  public void acceptAddContact(JID jid) {
    final String userID = getTransport().convertJIDToID(jid);
    Log.debug("XMPP: accept-add contact: " + userID);

    final Presence accept = new Presence(Type.subscribed);
    accept.setTo(userID);
    conn.sendPacket(accept);
  }

  /** @see net.sf.kraken.session.TransportSession#sendMessage(org.xmpp.packet.JID, String) */
  @Override
  public void sendMessage(JID jid, String message) {
    Chat chat = conn.getChatManager().createChat(getTransport().convertJIDToID(jid), listener);
    try {
      chat.sendMessage(message);
    } catch (XMPPException e) {
      // Ignore
    }
  }

  /**
   * @see net.sf.kraken.session.TransportSession#sendChatState(org.xmpp.packet.JID,
   *     net.sf.kraken.type.ChatStateType)
   */
  @Override
  public void sendChatState(JID jid, ChatStateType chatState) {
    final Presence presence = conn.getRoster().getPresence(jid.toString());
    if (presence == null || presence.getType().equals(Presence.Type.unavailable)) {
      // don't send chat state to contacts that are offline.
      return;
    }
    Chat chat = conn.getChatManager().createChat(getTransport().convertJIDToID(jid), listener);
    try {
      ChatState state = ChatState.active;
      switch (chatState) {
        case active:
          state = ChatState.active;
          break;
        case composing:
          state = ChatState.composing;
          break;
        case paused:
          state = ChatState.paused;
          break;
        case inactive:
          state = ChatState.inactive;
          break;
        case gone:
          state = ChatState.gone;
          break;
      }

      Message message = new Message();
      message.addExtension(new ChatStateExtension(state));
      chat.sendMessage(message);
    } catch (XMPPException e) {
      // Ignore
    }
  }

  /**
   * @see net.sf.kraken.session.TransportSession#sendBuzzNotification(org.xmpp.packet.JID, String)
   */
  @Override
  public void sendBuzzNotification(JID jid, String message) {
    Chat chat = conn.getChatManager().createChat(getTransport().convertJIDToID(jid), listener);
    try {
      Message m = new Message();
      m.setTo(getTransport().convertJIDToID(jid));
      m.addExtension(new BuzzExtension());
      chat.sendMessage(m);
    } catch (XMPPException e) {
      // Ignore
    }
  }

  /**
   * Returns a (legacy/Smack-based) Presence stanza that represents the current presence of this
   * session. The Presence includes relevant Mode, Status and VCardUpdate information.
   *
   * <p>This method uses the fields {@link TransportSession#presence} and {@link
   * TransportSession#verboseStatus} to generate the result.
   *
   * @return A Presence packet representing the current presence state of this session.
   */
  public Presence constructCurrentLegacyPresencePacket() {
    final org.jivesoftware.smack.packet.Presence presence =
        new org.jivesoftware.smack.packet.Presence(
            org.jivesoftware.smack.packet.Presence.Type.available);
    final Presence.Mode pMode =
        ((XMPPTransport) getTransport()).convertGatewayStatusToXMPP(this.presence);
    if (pMode != null) {
      presence.setMode(pMode);
    }
    if (verboseStatus != null && verboseStatus.trim().length() > 0) {
      presence.setStatus(verboseStatus);
    }
    final Avatar avatar = getAvatar();
    if (avatar != null) {
      final VCardUpdateExtension ext = new VCardUpdateExtension();
      ext.setPhotoHash(avatar.getLegacyIdentifier());
      presence.addExtension(ext);
    }
    return presence;
  }

  /** @see net.sf.kraken.session.TransportSession#updateLegacyAvatar(String, byte[]) */
  @Override
  public void updateLegacyAvatar(String type, final byte[] data) {
    new Thread() {
      @Override
      public void run() {
        Avatar avatar = getAvatar();

        VCard vCard = new VCard();
        try {
          vCard.load(conn);
          vCard.setAvatar(data, avatar.getMimeType());
          vCard.save(conn);

          avatar.setLegacyIdentifier(avatar.getXmppHash());

          // Same thing in this case, so lets go ahead and set them.
          final org.jivesoftware.smack.packet.Presence presence =
              constructCurrentLegacyPresencePacket();
          conn.sendPacket(presence);
        } catch (XMPPException e) {
          Log.debug("XMPP: Error while updating vcard for avatar change.", e);
        }
      }
    }.start();
  }

  private void syncUsers() {
    for (RosterEntry entry : conn.getRoster().getEntries()) {
      getBuddyManager()
          .storeBuddy(
              new XMPPBuddy(
                  getBuddyManager(), entry.getUser(), entry.getName(), entry.getGroups(), entry));
      // Facebook does not support presence probes in their XMPP implementation. See
      // http://developers.facebook.com/docs/chat#features
      if (!TransportType.facebook.equals(getTransport().getType())) {
        // ProbePacket probe = new ProbePacket(this.getJID()+"/"+xmppResource, entry.getUser());
        ProbePacket probe = new ProbePacket(null, entry.getUser());
        Log.debug("XMPP: Sending the following probe packet: " + probe.toXML());
        try {
          conn.sendPacket(probe);
        } catch (IllegalStateException e) {
          Log.debug("XMPP: Not connected while trying to send probe.");
        }
      }
    }

    try {
      getTransport().syncLegacyRoster(getJID(), getBuddyManager().getBuddies());
    } catch (UserNotFoundException ex) {
      Log.error("XMPP: User not found while syncing legacy roster: ", ex);
    }

    getBuddyManager().activate();

    // lets repoll the roster since smack seems to get out of sync...
    // we'll let the roster listener take care of this though.
    conn.getRoster().reload();
  }

  private class MailCheck extends TimerTask {
    /** Check GMail for new mail. */
    @Override
    public void run() {
      if (getTransport().getType().equals(TransportType.gtalk)
          && JiveGlobals.getBooleanProperty("plugin.gateway.gtalk.mailnotifications", true)) {
        GoogleMailNotifyExtension gmne = new GoogleMailNotifyExtension();
        gmne.setNewerThanTime(listener.getLastGMailThreadDate());
        gmne.setNewerThanTid(listener.getLastGMailThreadId());
        conn.sendPacket(
            new IQWithPacketExtension(generateFullJID(getRegistration().getUsername()), gmne));
      }
    }
  }
}
Ejemplo n.º 6
0
  /**
   * Creates new account.
   *
   * @param user full or bare jid.
   * @param password
   * @param accountType xmpp account type can be replaced depend on server part.
   * @param syncable
   * @param storePassword
   * @param useOrbot
   * @return assigned account name.
   * @throws NetworkException if user or server part are invalid.
   */
  public String addAccount(
      String user,
      String password,
      AccountType accountType,
      boolean syncable,
      boolean storePassword,
      boolean useOrbot)
      throws NetworkException {
    if (accountType.getProtocol().isOAuth()) {
      int index = 1;
      while (true) {
        user = String.valueOf(index);
        boolean found = false;
        for (AccountItem accountItem : accountItems.values())
          if (accountItem
                  .getConnectionSettings()
                  .getServerName()
                  .equals(accountType.getFirstServer())
              && accountItem.getConnectionSettings().getUserName().equals(user)) {
            found = true;
            break;
          }
        if (!found) break;
        index++;
      }
    }

    if (user == null) throw new NetworkException(R.string.EMPTY_USER_NAME);

    if (user.indexOf("@") == -1) {
      if ("".equals(accountType.getFirstServer()))
        throw new NetworkException(R.string.EMPTY_SERVER_NAME);
      else user += "@" + accountType.getFirstServer();
    }

    String serverName = StringUtils.parseServer(user);
    String userName = StringUtils.parseName(user);
    String resource = StringUtils.parseResource(user);
    String host = accountType.getHost();
    int port = accountType.getPort();
    boolean tlsRequired = accountType.isTLSRequired();
    if (useOrbot) tlsRequired = true;

    if ("".equals(serverName)) {
      throw new NetworkException(R.string.EMPTY_SERVER_NAME);
    } else if (!accountType.isAllowServer() && !serverName.equals(accountType.getFirstServer()))
      throw new NetworkException(R.string.INCORRECT_USER_NAME);

    if ("".equals(userName)) throw new NetworkException(R.string.EMPTY_USER_NAME);
    if ("".equals(resource)) resource = "android" + StringUtils.randomString(8);

    if (accountType.getId() == R.array.account_type_xmpp) {
      host = serverName;
      for (AccountType check : accountTypes)
        if (check.getServers().contains(serverName)) {
          accountType = check;
          host = check.getHost();
          port = check.getPort();
          tlsRequired = check.isTLSRequired();
          break;
        }
    }

    AccountItem accountItem;
    for (; ; ) {
      if (getAccount(userName + '@' + serverName + '/' + resource) == null) break;
      resource = "android" + StringUtils.randomString(8);
    }

    accountItem =
        addAccount(
            accountType.getProtocol(),
            true,
            host,
            port,
            serverName,
            userName,
            storePassword,
            password,
            resource,
            getNextColorIndex(),
            0,
            StatusMode.available,
            SettingsManager.statusText(),
            true,
            true,
            tlsRequired ? TLSMode.required : TLSMode.enabled,
            false,
            useOrbot ? ProxyType.orbot : ProxyType.none,
            "localhost",
            8080,
            "",
            "",
            syncable,
            null,
            null,
            ArchiveMode.available);
    onAccountChanged(accountItem.getAccount());
    if (accountItems.size() > 1 && SettingsManager.contactsEnableShowAccounts())
      SettingsManager.enableContactsShowAccount();
    return accountItem.getAccount();
  }
/**
 * A straightforward implementation of the basic instant messaging operation set.
 *
 * @author Damian Minkov
 * @author Matthieu Helleringer
 * @author Alain Knaebel
 * @author Emil Ivov
 * @author Hristo Terezov
 */
public class OperationSetBasicInstantMessagingJabberImpl
    extends AbstractOperationSetBasicInstantMessaging implements OperationSetMessageCorrection {
  /** Our class logger */
  private static final Logger logger =
      Logger.getLogger(OperationSetBasicInstantMessagingJabberImpl.class);

  /** The maximum number of unread threads that we'd be notifying the user of. */
  private static final String PNAME_MAX_GMAIL_THREADS_PER_NOTIFICATION =
      "net.java.sip.communicator.impl.protocol.jabber." + "MAX_GMAIL_THREADS_PER_NOTIFICATION";

  /**
   * A table mapping contact addresses to full jids that can be used to target a specific resource
   * (rather than sending a message to all logged instances of a user).
   */
  private Map<String, StoredThreadID> jids = new Hashtable<String, StoredThreadID>();

  /** The most recent full JID used for the contact address. */
  private Map<String, String> recentJIDForAddress = new Hashtable<String, String>();
  /**
   * The smackMessageListener instance listens for incoming messages. Keep a reference of it so if
   * anything goes wrong we don't add two different instances.
   */
  private SmackMessageListener smackMessageListener = null;

  /**
   * Contains the complete jid of a specific user and the time that it was last used so that we
   * could remove it after a certain point.
   */
  public static class StoredThreadID {
    /** The time that we last sent or received a message from this jid */
    long lastUpdatedTime;

    /** The last chat used, this way we will reuse the thread-id */
    String threadID;
  }

  /** A prefix helps to make sure that thread ID's are unique across mutliple instances. */
  private static String prefix = StringUtils.randomString(5);

  /**
   * Keeps track of the current increment, which is appended to the prefix to forum a unique thread
   * ID.
   */
  private static long id = 0;

  /**
   * The number of milliseconds that we preserve threads with no traffic before considering them
   * dead.
   */
  private static final long JID_INACTIVITY_TIMEOUT = 10 * 60 * 1000; // 10 min.

  /**
   * Indicates the time of the last Mailbox report that we received from Google (if this is a Google
   * server we are talking to). Should be included in all following mailbox queries
   */
  private long lastReceivedMailboxResultTime = -1;

  /** The provider that created us. */
  private final ProtocolProviderServiceJabberImpl jabberProvider;

  /**
   * A reference to the persistent presence operation set that we use to match incoming messages to
   * <tt>Contact</tt>s and vice versa.
   */
  private OperationSetPersistentPresenceJabberImpl opSetPersPresence = null;

  /** The opening BODY HTML TAG: &ltbody&gt */
  private static final String OPEN_BODY_TAG = "<body>";

  /** The closing BODY HTML TAG: &ltbody&gt */
  private static final String CLOSE_BODY_TAG = "</body>";

  /** The html namespace used as feature XHTMLManager.namespace */
  private static final String HTML_NAMESPACE = "http://jabber.org/protocol/xhtml-im";

  /** List of filters to be used to filter which messages to handle current Operation Set. */
  private List<PacketFilter> packetFilters = new ArrayList<PacketFilter>();

  /** Whether carbon is enabled or not. */
  private boolean isCarbonEnabled = false;

  /**
   * Creates an instance of this operation set.
   *
   * @param provider a reference to the <tt>ProtocolProviderServiceImpl</tt> that created us and
   *     that we'll use for retrieving the underlying aim connection.
   */
  OperationSetBasicInstantMessagingJabberImpl(ProtocolProviderServiceJabberImpl provider) {
    this.jabberProvider = provider;

    packetFilters.add(new GroupMessagePacketFilter());
    packetFilters.add(new PacketTypeFilter(org.jivesoftware.smack.packet.Message.class));

    provider.addRegistrationStateChangeListener(new RegistrationStateListener());

    ProviderManager man = ProviderManager.getInstance();
    MessageCorrectionExtensionProvider extProvider = new MessageCorrectionExtensionProvider();
    man.addExtensionProvider(
        MessageCorrectionExtension.ELEMENT_NAME, MessageCorrectionExtension.NAMESPACE, extProvider);
  }

  /**
   * Create a Message instance with the specified UID, content type and a default encoding. This
   * method can be useful when message correction is required. One can construct the corrected
   * message to have the same UID as the message before correction.
   *
   * @param messageText the string content of the message.
   * @param contentType the MIME-type for <tt>content</tt>
   * @param messageUID the unique identifier of this message.
   * @return Message the newly created message
   */
  public Message createMessageWithUID(String messageText, String contentType, String messageUID) {
    return new MessageJabberImpl(messageText, contentType, DEFAULT_MIME_ENCODING, null, messageUID);
  }

  /**
   * Create a Message instance for sending arbitrary MIME-encoding content.
   *
   * @param content content value
   * @param contentType the MIME-type for <tt>content</tt>
   * @return the newly created message.
   */
  public Message createMessage(String content, String contentType) {
    return createMessage(content, contentType, DEFAULT_MIME_ENCODING, null);
  }

  /**
   * Create a Message instance for sending arbitrary MIME-encoding content.
   *
   * @param content content value
   * @param contentType the MIME-type for <tt>content</tt>
   * @param subject the Subject of the message that we'd like to create.
   * @param encoding the enconding of the message that we will be sending.
   * @return the newly created message.
   */
  @Override
  public Message createMessage(
      String content, String contentType, String encoding, String subject) {
    return new MessageJabberImpl(content, contentType, encoding, subject);
  }

  Message createMessage(String content, String contentType, String messageUID) {
    return new MessageJabberImpl(content, contentType, DEFAULT_MIME_ENCODING, null, messageUID);
  }

  /**
   * Determines wheter the protocol provider (or the protocol itself) support sending and receiving
   * offline messages. Most often this method would return true for protocols that support offline
   * messages and false for those that don't. It is however possible for a protocol to support these
   * messages and yet have a particular account that does not (i.e. feature not enabled on the
   * protocol server). In cases like this it is possible for this method to return true even when
   * offline messaging is not supported, and then have the sendMessage method throw an
   * OperationFailedException with code - OFFLINE_MESSAGES_NOT_SUPPORTED.
   *
   * @return <tt>true</tt> if the protocol supports offline messages and <tt>false</tt> otherwise.
   */
  public boolean isOfflineMessagingSupported() {
    return true;
  }

  /**
   * Determines wheter the protocol supports the supplied content type
   *
   * @param contentType the type we want to check
   * @return <tt>true</tt> if the protocol supports it and <tt>false</tt> otherwise.
   */
  public boolean isContentTypeSupported(String contentType) {
    return (contentType.equals(DEFAULT_MIME_TYPE) || contentType.equals(HTML_MIME_TYPE));
  }

  /**
   * Determines whether the protocol supports the supplied content type for the given contact.
   *
   * @param contentType the type we want to check
   * @param contact contact which is checked for supported contentType
   * @return <tt>true</tt> if the contact supports it and <tt>false</tt> otherwise.
   */
  @Override
  public boolean isContentTypeSupported(String contentType, Contact contact) {
    // by default we support default mime type, for other mimetypes
    // method must be overriden
    if (contentType.equals(DEFAULT_MIME_TYPE)) return true;
    else if (contentType.equals(HTML_MIME_TYPE)) {
      String toJID = recentJIDForAddress.get(contact.getAddress());

      if (toJID == null) toJID = contact.getAddress();

      return jabberProvider.isFeatureListSupported(toJID, HTML_NAMESPACE);
    }

    return false;
  }

  /**
   * Remove from our <tt>jids</tt> map all entries that have not seen any activity (i.e. neither
   * outgoing nor incoming messags) for more than JID_INACTIVITY_TIMEOUT. Note that this method is
   * not synchronous and that it is only meant for use by the {@link #getThreadIDForAddress(String)}
   * and {@link #putJidForAddress(String, String)}
   */
  private void purgeOldJids() {
    long currentTime = System.currentTimeMillis();

    Iterator<Map.Entry<String, StoredThreadID>> entries = jids.entrySet().iterator();

    while (entries.hasNext()) {
      Map.Entry<String, StoredThreadID> entry = entries.next();
      StoredThreadID target = entry.getValue();

      if (currentTime - target.lastUpdatedTime > JID_INACTIVITY_TIMEOUT) entries.remove();
    }
  }

  /**
   * Returns the last jid that the party with the specified <tt>address</tt> contacted us from or
   * <tt>null</tt>(or bare jid) if we don't have a jid for the specified <tt>address</tt> yet. The
   * method would also purge all entries that haven't seen any activity (i.e. no one has tried to
   * get or remap it) for a delay longer than <tt>JID_INACTIVITY_TIMEOUT</tt>.
   *
   * @param jid the <tt>jid</tt> that we'd like to obtain a threadID for.
   * @return the last jid that the party with the specified <tt>address</tt> contacted us from or
   *     <tt>null</tt> if we don't have a jid for the specified <tt>address</tt> yet.
   */
  String getThreadIDForAddress(String jid) {
    synchronized (jids) {
      purgeOldJids();
      StoredThreadID ta = jids.get(jid);

      if (ta == null) return null;

      ta.lastUpdatedTime = System.currentTimeMillis();

      return ta.threadID;
    }
  }

  /**
   * Maps the specified <tt>address</tt> to <tt>jid</tt>. The point of this method is to allow us to
   * send all messages destined to the contact with the specified <tt>address</tt> to the
   * <tt>jid</tt> that they last contacted us from.
   *
   * @param threadID the threadID of conversation.
   * @param jid the jid (i.e. address/resource) that the contact with the specified <tt>address</tt>
   *     last contacted us from.
   */
  private void putJidForAddress(String jid, String threadID) {
    synchronized (jids) {
      purgeOldJids();

      StoredThreadID ta = jids.get(jid);

      if (ta == null) {
        ta = new StoredThreadID();
        jids.put(jid, ta);
      }

      recentJIDForAddress.put(StringUtils.parseBareAddress(jid), jid);

      ta.lastUpdatedTime = System.currentTimeMillis();
      ta.threadID = threadID;
    }
  }

  /**
   * Helper function used to send a message to a contact, with the given extensions attached.
   *
   * @param to The contact to send the message to.
   * @param toResource The resource to send the message to or null if no resource has been specified
   * @param message The message to send.
   * @param extensions The XMPP extensions that should be attached to the message before sending.
   * @return The MessageDeliveryEvent that resulted after attempting to send this message, so the
   *     calling function can modify it if needed.
   */
  private MessageDeliveredEvent sendMessage(
      Contact to, ContactResource toResource, Message message, PacketExtension[] extensions) {
    if (!(to instanceof ContactJabberImpl))
      throw new IllegalArgumentException("The specified contact is not a Jabber contact." + to);

    assertConnected();

    org.jivesoftware.smack.packet.Message msg = new org.jivesoftware.smack.packet.Message();

    String toJID = null;

    if (toResource != null) {
      if (toResource.equals(ContactResource.BASE_RESOURCE)) {
        toJID = to.getAddress();
      } else toJID = ((ContactResourceJabberImpl) toResource).getFullJid();
    }

    if (toJID == null) {
      toJID = to.getAddress();
    }

    msg.setPacketID(message.getMessageUID());
    msg.setTo(toJID);

    for (PacketExtension ext : extensions) {
      msg.addExtension(ext);
    }

    if (logger.isTraceEnabled())
      logger.trace("Will send a message to:" + toJID + " chat.jid=" + toJID);

    MessageDeliveredEvent msgDeliveryPendingEvt =
        new MessageDeliveredEvent(message, to, toResource);

    MessageDeliveredEvent[] transformedEvents =
        messageDeliveryPendingTransform(msgDeliveryPendingEvt);

    if (transformedEvents == null || transformedEvents.length == 0) return null;

    for (MessageDeliveredEvent event : transformedEvents) {
      String content = event.getSourceMessage().getContent();

      if (message.getContentType().equals(HTML_MIME_TYPE)) {
        msg.setBody(Html2Text.extractText(content));

        // Check if the other user supports XHTML messages
        // make sure we use our discovery manager as it caches calls
        if (jabberProvider.isFeatureListSupported(toJID, HTML_NAMESPACE)) {
          // Add the XHTML text to the message
          XHTMLManager.addBody(msg, OPEN_BODY_TAG + content + CLOSE_BODY_TAG);
        }
      } else {
        // this is plain text so keep it as it is.
        msg.setBody(content);
      }

      // msg.addExtension(new Version());

      if (event.isMessageEncrypted() && isCarbonEnabled) {
        msg.addExtension(new CarbonPacketExtension.PrivateExtension());
      }

      MessageEventManager.addNotificationsRequests(msg, true, false, false, true);

      String threadID = getThreadIDForAddress(toJID);
      if (threadID == null) threadID = nextThreadID();

      msg.setThread(threadID);
      msg.setType(org.jivesoftware.smack.packet.Message.Type.chat);
      msg.setFrom(jabberProvider.getConnection().getUser());

      jabberProvider.getConnection().sendPacket(msg);

      putJidForAddress(toJID, threadID);
    }

    return new MessageDeliveredEvent(message, to, toResource);
  }

  /**
   * Sends the <tt>message</tt> to the destination indicated by the <tt>to</tt> contact.
   *
   * @param to the <tt>Contact</tt> to send <tt>message</tt> to
   * @param message the <tt>Message</tt> to send.
   * @throws java.lang.IllegalStateException if the underlying stack is not registered and
   *     initialized.
   * @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an instance of ContactImpl.
   */
  public void sendInstantMessage(Contact to, Message message)
      throws IllegalStateException, IllegalArgumentException {
    sendInstantMessage(to, null, message);
  }

  /**
   * Sends the <tt>message</tt> to the destination indicated by the <tt>to</tt>. Provides a default
   * implementation of this method.
   *
   * @param to the <tt>Contact</tt> to send <tt>message</tt> to
   * @param toResource the resource to which the message should be send
   * @param message the <tt>Message</tt> to send.
   * @throws java.lang.IllegalStateException if the underlying ICQ stack is not registered and
   *     initialized.
   * @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an instance belonging to the
   *     underlying implementation.
   */
  @Override
  public void sendInstantMessage(Contact to, ContactResource toResource, Message message)
      throws IllegalStateException, IllegalArgumentException {
    MessageDeliveredEvent msgDelivered =
        sendMessage(to, toResource, message, new PacketExtension[0]);

    fireMessageEvent(msgDelivered);
  }

  /**
   * Replaces the message with ID <tt>correctedMessageUID</tt> sent to the contact <tt>to</tt> with
   * the message <tt>message</tt>
   *
   * @param to The contact to send the message to.
   * @param message The new message.
   * @param correctedMessageUID The ID of the message being replaced.
   */
  public void correctMessage(
      Contact to, ContactResource resource, Message message, String correctedMessageUID) {
    PacketExtension[] exts = new PacketExtension[1];
    exts[0] = new MessageCorrectionExtension(correctedMessageUID);
    MessageDeliveredEvent msgDelivered = sendMessage(to, resource, message, exts);
    msgDelivered.setCorrectedMessageUID(correctedMessageUID);
    fireMessageEvent(msgDelivered);
  }

  /**
   * Utility method throwing an exception if the stack is not properly initialized.
   *
   * @throws java.lang.IllegalStateException if the underlying stack is not registered and
   *     initialized.
   */
  private void assertConnected() throws IllegalStateException {
    if (opSetPersPresence == null) {
      throw new IllegalStateException(
          "The provider must be signed on the service before" + " being able to communicate.");
    } else opSetPersPresence.assertConnected();
  }

  /** Our listener that will tell us when we're registered to */
  private class RegistrationStateListener implements RegistrationStateChangeListener {
    /**
     * The method is called by a ProtocolProvider implementation whenever a change in the
     * registration state of the corresponding provider had occurred.
     *
     * @param evt ProviderStatusChangeEvent the event describing the status change.
     */
    public void registrationStateChanged(RegistrationStateChangeEvent evt) {
      if (logger.isDebugEnabled())
        logger.debug(
            "The provider changed state from: " + evt.getOldState() + " to: " + evt.getNewState());

      if (evt.getNewState() == RegistrationState.REGISTERING) {
        opSetPersPresence =
            (OperationSetPersistentPresenceJabberImpl)
                jabberProvider.getOperationSet(OperationSetPersistentPresence.class);

        if (smackMessageListener == null) {
          smackMessageListener = new SmackMessageListener();
        } else {
          // make sure this listener is not already installed in this
          // connection
          jabberProvider.getConnection().removePacketListener(smackMessageListener);
        }

        jabberProvider
            .getConnection()
            .addPacketListener(
                smackMessageListener,
                new AndFilter(packetFilters.toArray(new PacketFilter[packetFilters.size()])));
      } else if (evt.getNewState() == RegistrationState.REGISTERED) {
        new Thread(
                new Runnable() {
                  @Override
                  public void run() {
                    initAdditionalServices();
                  }
                })
            .start();
      } else if (evt.getNewState() == RegistrationState.UNREGISTERED
          || evt.getNewState() == RegistrationState.CONNECTION_FAILED
          || evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED) {
        if (jabberProvider.getConnection() != null) {
          if (smackMessageListener != null)
            jabberProvider.getConnection().removePacketListener(smackMessageListener);
        }

        smackMessageListener = null;
      }
    }
  }

  /** Initialize additional services, like gmail notifications and message carbons. */
  private void initAdditionalServices() {
    // subscribe for Google (Gmail or Google Apps) notifications
    // for new mail messages.
    boolean enableGmailNotifications =
        jabberProvider
            .getAccountID()
            .getAccountPropertyBoolean("GMAIL_NOTIFICATIONS_ENABLED", false);

    if (enableGmailNotifications) subscribeForGmailNotifications();

    boolean enableCarbon =
        isCarbonSupported()
            && !jabberProvider
                .getAccountID()
                .getAccountPropertyBoolean(ProtocolProviderFactory.IS_CARBON_DISABLED, false);
    if (enableCarbon) {
      enableDisableCarbon(true);
    } else {
      isCarbonEnabled = false;
    }
  }

  /**
   * Sends enable or disable carbon packet to the server.
   *
   * @param enable if <tt>true</tt> sends enable packet otherwise sends disable packet.
   */
  private void enableDisableCarbon(final boolean enable) {
    IQ iq =
        new IQ() {

          @Override
          public String getChildElementXML() {
            return "<" + (enable ? "enable" : "disable") + " xmlns='urn:xmpp:carbons:2' />";
          }
        };

    Packet response = null;
    try {
      PacketCollector packetCollector =
          jabberProvider
              .getConnection()
              .createPacketCollector(new PacketIDFilter(iq.getPacketID()));
      iq.setFrom(jabberProvider.getOurJID());
      iq.setType(IQ.Type.SET);
      jabberProvider.getConnection().sendPacket(iq);
      response = packetCollector.nextResult(SmackConfiguration.getPacketReplyTimeout());

      packetCollector.cancel();
    } catch (Exception e) {
      logger.error("Failed to enable carbon.", e);
    }

    isCarbonEnabled = false;

    if (response == null) {
      logger.error("Failed to enable carbon. No response is received.");
    } else if (response.getError() != null) {
      logger.error("Failed to enable carbon: " + response.getError());
    } else if (!(response instanceof IQ) || !((IQ) response).getType().equals(IQ.Type.RESULT)) {
      logger.error("Failed to enable carbon. The response is not correct.");
    } else {
      isCarbonEnabled = true;
    }
  }

  /**
   * Checks whether the carbon is supported by the server or not.
   *
   * @return <tt>true</tt> if carbon is supported by the server and <tt>false</tt> if not.
   */
  private boolean isCarbonSupported() {
    try {
      return jabberProvider
          .getDiscoveryManager()
          .discoverInfo(jabberProvider.getAccountID().getService())
          .containsFeature(CarbonPacketExtension.NAMESPACE);
    } catch (XMPPException e) {
      logger.warn("Failed to retrieve carbon support." + e.getMessage());
    }
    return false;
  }

  /** The listener that we use in order to handle incoming messages. */
  @SuppressWarnings("unchecked")
  private class SmackMessageListener implements PacketListener {
    /**
     * Handles incoming messages and dispatches whatever events are necessary.
     *
     * @param packet the packet that we need to handle (if it is a message).
     */
    public void processPacket(Packet packet) {
      if (!(packet instanceof org.jivesoftware.smack.packet.Message)) return;

      org.jivesoftware.smack.packet.Message msg = (org.jivesoftware.smack.packet.Message) packet;

      boolean isForwardedSentMessage = false;
      if (msg.getBody() == null) {

        CarbonPacketExtension carbonExt =
            (CarbonPacketExtension) msg.getExtension(CarbonPacketExtension.NAMESPACE);
        if (carbonExt == null) return;

        isForwardedSentMessage =
            (carbonExt.getElementName() == CarbonPacketExtension.SENT_ELEMENT_NAME);
        List<ForwardedPacketExtension> extensions =
            carbonExt.getChildExtensionsOfType(ForwardedPacketExtension.class);
        if (extensions.isEmpty()) return;
        ForwardedPacketExtension forwardedExt = extensions.get(0);
        msg = forwardedExt.getMessage();
        if (msg == null || msg.getBody() == null) return;
      }

      Object multiChatExtension = msg.getExtension("x", "http://jabber.org/protocol/muc#user");

      // its not for us
      if (multiChatExtension != null) return;

      String userFullId = isForwardedSentMessage ? msg.getTo() : msg.getFrom();

      String userBareID = StringUtils.parseBareAddress(userFullId);

      boolean isPrivateMessaging = false;
      ChatRoom privateContactRoom = null;
      OperationSetMultiUserChatJabberImpl mucOpSet =
          (OperationSetMultiUserChatJabberImpl)
              jabberProvider.getOperationSet(OperationSetMultiUserChat.class);
      if (mucOpSet != null) privateContactRoom = mucOpSet.getChatRoom(userBareID);

      if (privateContactRoom != null) {
        isPrivateMessaging = true;
      }

      if (logger.isDebugEnabled()) {
        if (logger.isDebugEnabled())
          logger.debug("Received from " + userBareID + " the message " + msg.toXML());
      }

      Message newMessage = createMessage(msg.getBody(), DEFAULT_MIME_TYPE, msg.getPacketID());

      // check if the message is available in xhtml
      PacketExtension ext = msg.getExtension("http://jabber.org/protocol/xhtml-im");

      if (ext != null) {
        XHTMLExtension xhtmlExt = (XHTMLExtension) ext;

        // parse all bodies
        Iterator<String> bodies = xhtmlExt.getBodies();
        StringBuffer messageBuff = new StringBuffer();
        while (bodies.hasNext()) {
          String body = bodies.next();
          messageBuff.append(body);
        }

        if (messageBuff.length() > 0) {
          // we remove body tags around message cause their
          // end body tag is breaking
          // the visualization as html in the UI
          String receivedMessage =
              messageBuff
                  .toString()
                  // removes body start tag
                  .replaceAll("\\<[bB][oO][dD][yY].*?>", "")
                  // removes body end tag
                  .replaceAll("\\</[bB][oO][dD][yY].*?>", "");

          // for some reason &apos; is not rendered correctly
          // from our ui, lets use its equivalent. Other
          // similar chars(< > & ") seem ok.
          receivedMessage = receivedMessage.replaceAll("&apos;", "&#39;");

          newMessage = createMessage(receivedMessage, HTML_MIME_TYPE, msg.getPacketID());
        }
      }

      PacketExtension correctionExtension = msg.getExtension(MessageCorrectionExtension.NAMESPACE);
      String correctedMessageUID = null;
      if (correctionExtension != null) {
        correctedMessageUID =
            ((MessageCorrectionExtension) correctionExtension).getCorrectedMessageUID();
      }

      Contact sourceContact =
          opSetPersPresence.findContactByID((isPrivateMessaging ? userFullId : userBareID));
      if (msg.getType() == org.jivesoftware.smack.packet.Message.Type.error) {
        // error which is multichat and we don't know about the contact
        // is a muc message error which is missing muc extension
        // and is coming from the room, when we try to send message to
        // room which was deleted or offline on the server
        if (isPrivateMessaging && sourceContact == null) {
          if (privateContactRoom != null) {
            XMPPError error = packet.getError();
            int errorResultCode = ChatRoomMessageDeliveryFailedEvent.UNKNOWN_ERROR;

            if (error != null && error.getCode() == 403) {
              errorResultCode = ChatRoomMessageDeliveryFailedEvent.FORBIDDEN;
            }

            String errorReason = error.getMessage();

            ChatRoomMessageDeliveryFailedEvent evt =
                new ChatRoomMessageDeliveryFailedEvent(
                    privateContactRoom, null, errorResultCode, errorReason, new Date(), newMessage);
            ((ChatRoomJabberImpl) privateContactRoom).fireMessageEvent(evt);
          }

          return;
        }

        if (logger.isInfoEnabled()) logger.info("Message error received from " + userBareID);

        int errorResultCode = MessageDeliveryFailedEvent.UNKNOWN_ERROR;
        if (packet.getError() != null) {
          int errorCode = packet.getError().getCode();

          if (errorCode == 503) {
            org.jivesoftware.smackx.packet.MessageEvent msgEvent =
                (org.jivesoftware.smackx.packet.MessageEvent)
                    packet.getExtension("x", "jabber:x:event");
            if (msgEvent != null && msgEvent.isOffline()) {
              errorResultCode = MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED;
            }
          }
        }

        if (sourceContact == null) {
          sourceContact = opSetPersPresence.createVolatileContact(userFullId, isPrivateMessaging);
        }

        MessageDeliveryFailedEvent ev =
            new MessageDeliveryFailedEvent(
                newMessage, sourceContact, correctedMessageUID, errorResultCode);

        // ev = messageDeliveryFailedTransform(ev);

        if (ev != null) fireMessageEvent(ev);
        return;
      }
      putJidForAddress(userFullId, msg.getThread());

      // In the second condition we filter all group chat messages,
      // because they are managed by the multi user chat operation set.
      if (sourceContact == null) {
        if (logger.isDebugEnabled())
          logger.debug("received a message from an unknown contact: " + userBareID);
        // create the volatile contact
        sourceContact = opSetPersPresence.createVolatileContact(userFullId, isPrivateMessaging);
      }

      Date timestamp = new Date();
      // Check for XEP-0091 timestamp (deprecated)
      PacketExtension delay = msg.getExtension("x", "jabber:x:delay");
      if (delay != null && delay instanceof DelayInformation) {
        timestamp = ((DelayInformation) delay).getStamp();
      }
      // check for XEP-0203 timestamp
      delay = msg.getExtension("delay", "urn:xmpp:delay");
      if (delay != null && delay instanceof DelayInfo) {
        timestamp = ((DelayInfo) delay).getStamp();
      }

      ContactResource resource = ((ContactJabberImpl) sourceContact).getResourceFromJid(userFullId);

      EventObject msgEvt = null;
      if (!isForwardedSentMessage)
        msgEvt =
            new MessageReceivedEvent(
                newMessage,
                sourceContact,
                resource,
                timestamp,
                correctedMessageUID,
                isPrivateMessaging,
                privateContactRoom);
      else msgEvt = new MessageDeliveredEvent(newMessage, sourceContact, timestamp);
      // msgReceivedEvt = messageReceivedTransform(msgReceivedEvt);
      if (msgEvt != null) fireMessageEvent(msgEvt);
    }
  }

  /** A filter that prevents this operation set from handling multi user chat messages. */
  private static class GroupMessagePacketFilter implements PacketFilter {
    /**
     * Returns <tt>true</tt> if <tt>packet</tt> is a <tt>Message</tt> and false otherwise.
     *
     * @param packet the packet that we need to check.
     * @return <tt>true</tt> if <tt>packet</tt> is a <tt>Message</tt> and false otherwise.
     */
    public boolean accept(Packet packet) {
      if (!(packet instanceof org.jivesoftware.smack.packet.Message)) return false;

      org.jivesoftware.smack.packet.Message msg = (org.jivesoftware.smack.packet.Message) packet;

      return !msg.getType().equals(org.jivesoftware.smack.packet.Message.Type.groupchat);
    }
  }

  /**
   * Subscribes this provider as interested in receiving notifications for new mail messages from
   * Google mail services such as Gmail or Google Apps.
   */
  private void subscribeForGmailNotifications() {
    // first check support for the notification service
    String accountIDService = jabberProvider.getAccountID().getService();
    boolean notificationsAreSupported =
        jabberProvider.isFeatureSupported(accountIDService, NewMailNotificationIQ.NAMESPACE);

    if (!notificationsAreSupported) {
      if (logger.isDebugEnabled())
        logger.debug(
            accountIDService
                + " does not seem to provide a Gmail notification "
                + " service so we won't be trying to subscribe for it");
      return;
    }

    if (logger.isDebugEnabled())
      logger.debug(
          accountIDService
              + " seems to provide a Gmail notification "
              + " service so we will try to subscribe for it");

    ProviderManager providerManager = ProviderManager.getInstance();

    providerManager.addIQProvider(
        MailboxIQ.ELEMENT_NAME, MailboxIQ.NAMESPACE, new MailboxIQProvider());
    providerManager.addIQProvider(
        NewMailNotificationIQ.ELEMENT_NAME,
        NewMailNotificationIQ.NAMESPACE,
        new NewMailNotificationProvider());

    Connection connection = jabberProvider.getConnection();

    connection.addPacketListener(new MailboxIQListener(), new PacketTypeFilter(MailboxIQ.class));
    connection.addPacketListener(
        new NewMailNotificationListener(), new PacketTypeFilter(NewMailNotificationIQ.class));

    if (opSetPersPresence.getCurrentStatusMessage().equals(JabberStatusEnum.OFFLINE)) return;

    // create a query with -1 values for newer-than-tid and
    // newer-than-time attributes
    MailboxQueryIQ mailboxQuery = new MailboxQueryIQ();

    if (logger.isTraceEnabled())
      logger.trace(
          "sending mailNotification for acc: "
              + jabberProvider.getAccountID().getAccountUniqueID());
    jabberProvider.getConnection().sendPacket(mailboxQuery);
  }

  /**
   * Creates an html description of the specified mailbox.
   *
   * @param mailboxIQ the mailboxIQ that we are to describe.
   * @return an html description of <tt>mailboxIQ</tt>
   */
  private String createMailboxDescription(MailboxIQ mailboxIQ) {
    int threadCount = mailboxIQ.getThreadCount();

    String resourceHeaderKey =
        threadCount > 1 ? "service.gui.NEW_GMAIL_MANY_HEADER" : "service.gui.NEW_GMAIL_HEADER";

    String resourceFooterKey =
        threadCount > 1 ? "service.gui.NEW_GMAIL_MANY_FOOTER" : "service.gui.NEW_GMAIL_FOOTER";

    // FIXME Escape HTML!
    String newMailHeader =
        JabberActivator.getResources()
            .getI18NString(
                resourceHeaderKey,
                new String[] {
                  jabberProvider.getAccountID().getService(), // {0} - service name
                  mailboxIQ.getUrl(), // {1} - inbox URI
                  Integer.toString(threadCount) // {2} - thread count
                });

    StringBuilder message = new StringBuilder(newMailHeader);

    // we now start an html table for the threads.
    message.append("<table width=100% cellpadding=2 cellspacing=0 ");
    message.append("border=0 bgcolor=#e8eef7>");

    Iterator<MailThreadInfo> threads = mailboxIQ.threads();

    String maxThreadsStr =
        (String)
            JabberActivator.getConfigurationService()
                .getProperty(PNAME_MAX_GMAIL_THREADS_PER_NOTIFICATION);

    int maxThreads = 5;

    try {
      if (maxThreadsStr != null) maxThreads = Integer.parseInt(maxThreadsStr);
    } catch (NumberFormatException e) {
      if (logger.isDebugEnabled())
        logger.debug("Failed to parse max threads count: " + maxThreads + ". Going for default.");
    }

    // print a maximum of MAX_THREADS
    for (int i = 0; i < maxThreads && threads.hasNext(); i++) {
      message.append(threads.next().createHtmlDescription());
    }
    message.append("</table><br/>");

    if (threadCount > maxThreads) {
      String messageFooter =
          JabberActivator.getResources()
              .getI18NString(
                  resourceFooterKey,
                  new String[] {
                    mailboxIQ.getUrl(), // {0} - inbox URI
                    Integer.toString(threadCount - maxThreads) // {1} - thread count
                  });
      message.append(messageFooter);
    }

    return message.toString();
  }

  public String getRecentJIDForAddress(String address) {
    return recentJIDForAddress.get(address);
  }

  /** Receives incoming MailNotification Packets */
  private class MailboxIQListener implements PacketListener {
    /**
     * Handles incoming <tt>MailboxIQ</tt> packets.
     *
     * @param packet the IQ that we need to handle in case it is a <tt>MailboxIQ</tt>.
     */
    public void processPacket(Packet packet) {
      if (packet != null && !(packet instanceof MailboxIQ)) return;

      MailboxIQ mailboxIQ = (MailboxIQ) packet;

      if (mailboxIQ.getTotalMatched() < 1) return;

      // Get a reference to a dummy volatile contact
      Contact sourceContact =
          opSetPersPresence.findContactByID(jabberProvider.getAccountID().getService());

      if (sourceContact == null)
        sourceContact =
            opSetPersPresence.createVolatileContact(jabberProvider.getAccountID().getService());

      lastReceivedMailboxResultTime = mailboxIQ.getResultTime();

      String newMail = createMailboxDescription(mailboxIQ);

      Message newMailMessage =
          new MessageJabberImpl(newMail, HTML_MIME_TYPE, DEFAULT_MIME_ENCODING, null);

      MessageReceivedEvent msgReceivedEvt =
          new MessageReceivedEvent(
              newMailMessage,
              sourceContact,
              new Date(),
              MessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED);

      fireMessageEvent(msgReceivedEvt);
    }
  }

  /** Receives incoming NewMailNotification Packets. */
  private class NewMailNotificationListener implements PacketListener {
    /**
     * Handles incoming <tt>NewMailNotificationIQ</tt> packets.
     *
     * @param packet the IQ that we need to handle in case it is a <tt>NewMailNotificationIQ</tt>.
     */
    public void processPacket(Packet packet) {
      if (packet != null && !(packet instanceof NewMailNotificationIQ)) return;

      // check whether we are still enabled.
      boolean enableGmailNotifications =
          jabberProvider
              .getAccountID()
              .getAccountPropertyBoolean("GMAIL_NOTIFICATIONS_ENABLED", false);

      if (!enableGmailNotifications) return;

      if (opSetPersPresence.getCurrentStatusMessage().equals(JabberStatusEnum.OFFLINE)) return;

      MailboxQueryIQ mailboxQueryIQ = new MailboxQueryIQ();

      if (lastReceivedMailboxResultTime != -1)
        mailboxQueryIQ.setNewerThanTime(lastReceivedMailboxResultTime);

      if (logger.isTraceEnabled())
        logger.trace(
            "send mailNotification for acc: " + jabberProvider.getAccountID().getAccountUniqueID());

      jabberProvider.getConnection().sendPacket(mailboxQueryIQ);
    }
  }

  /**
   * Returns the inactivity timeout in milliseconds.
   *
   * @return The inactivity timeout in milliseconds. Or -1 if undefined
   */
  public long getInactivityTimeout() {
    return JID_INACTIVITY_TIMEOUT;
  }

  /**
   * Adds additional filters for incoming messages. To be able to skip some messages.
   *
   * @param filter to add
   */
  public void addMessageFilters(PacketFilter filter) {
    this.packetFilters.add(filter);
  }

  /**
   * Returns the next unique thread id. Each thread id made up of a short alphanumeric prefix along
   * with a unique numeric value.
   *
   * @return the next thread id.
   */
  public static synchronized String nextThreadID() {
    return prefix + Long.toString(id++);
  }
}
Ejemplo n.º 8
0
  /**
   * Process the AdHoc-Command packet that request the execution of some action of a command. If
   * this is the first request, this method checks, before executing the command, if:
   *
   * <ul>
   *   <li>The requested command exists
   *   <li>The requester has permissions to execute it
   *   <li>The command has more than one stage, if so, it saves the command and session ID for
   *       further use
   * </ul>
   *
   * <br>
   * <br>
   * If this is not the first request, this method checks, before executing the command, if:
   *
   * <ul>
   *   <li>The session ID of the request was stored
   *   <li>The session life do not exceed the time out
   *   <li>The action to execute is one of the available actions
   * </ul>
   *
   * @param requestData the packet to process.
   */
  private void processAdHocCommand(AdHocCommandData requestData) {
    // Only process requests of type SET
    if (requestData.getType() != IQ.Type.SET) {
      return;
    }

    // Creates the response with the corresponding data
    AdHocCommandData response = new AdHocCommandData();
    response.setTo(requestData.getFrom());
    response.setPacketID(requestData.getPacketID());
    response.setNode(requestData.getNode());
    response.setId(requestData.getTo());

    String sessionId = requestData.getSessionID();
    String commandNode = requestData.getNode();

    if (sessionId == null) {
      // A new execution request has been received. Check that the
      // command exists
      if (!commands.containsKey(commandNode)) {
        // Requested command does not exist so return
        // item_not_found error.
        respondError(response, XMPPError.Condition.item_not_found);
        return;
      }

      // Create new session ID
      sessionId = StringUtils.randomString(15);

      try {
        // Create a new instance of the command with the
        // corresponding sessioid
        LocalCommand command = newInstanceOfCmd(commandNode, sessionId);

        response.setType(IQ.Type.RESULT);
        command.setData(response);

        // Check that the requester has enough permission.
        // Answer forbidden error if requester permissions are not
        // enough to execute the requested command
        if (!command.hasPermission(requestData.getFrom())) {
          respondError(response, XMPPError.Condition.forbidden);
          return;
        }

        Action action = requestData.getAction();

        // If the action is unknown then respond an error.
        if (action != null && action.equals(Action.unknown)) {
          respondError(
              response,
              XMPPError.Condition.bad_request,
              AdHocCommand.SpecificErrorCondition.malformedAction);
          return;
        }

        // If the action is not execute, then it is an invalid action.
        if (action != null && !action.equals(Action.execute)) {
          respondError(
              response,
              XMPPError.Condition.bad_request,
              AdHocCommand.SpecificErrorCondition.badAction);
          return;
        }

        // Increase the state number, so the command knows in witch
        // stage it is
        command.incrementStage();
        // Executes the command
        command.execute();

        if (command.isLastStage()) {
          // If there is only one stage then the command is completed
          response.setStatus(Status.completed);
        } else {
          // Else it is still executing, and is registered to be
          // available for the next call
          response.setStatus(Status.executing);
          executingCommands.put(sessionId, command);
          // See if the session reaping thread is started. If not, start it.
          if (!sessionsSweeper.isAlive()) {
            sessionsSweeper.start();
          }
        }

        // Sends the response packet
        connection.sendPacket(response);

      } catch (XMPPException e) {
        // If there is an exception caused by the next, complete,
        // prev or cancel method, then that error is returned to the
        // requester.
        XMPPError error = e.getXMPPError();

        // If the error type is cancel, then the execution is
        // canceled therefore the status must show that, and the
        // command be removed from the executing list.
        if (XMPPError.Type.CANCEL.equals(error.getType())) {
          response.setStatus(Status.canceled);
          executingCommands.remove(sessionId);
        }
        respondError(response, error);
        e.printStackTrace();
      }
    } else {
      LocalCommand command = executingCommands.get(sessionId);

      // Check that a command exists for the specified sessionID
      // This also handles if the command was removed in the meanwhile
      // of getting the key and the value of the map.
      if (command == null) {
        respondError(
            response,
            XMPPError.Condition.bad_request,
            AdHocCommand.SpecificErrorCondition.badSessionid);
        return;
      }

      // Check if the Session data has expired (default is 10 minutes)
      long creationStamp = command.getCreationDate();
      if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000) {
        // Remove the expired session
        executingCommands.remove(sessionId);

        // Answer a not_allowed error (session-expired)
        respondError(
            response,
            XMPPError.Condition.not_allowed,
            AdHocCommand.SpecificErrorCondition.sessionExpired);
        return;
      }

      /*
       * Since the requester could send two requests for the same
       * executing command i.e. the same session id, all the execution of
       * the action must be synchronized to avoid inconsistencies.
       */
      synchronized (command) {
        Action action = requestData.getAction();

        // If the action is unknown the respond an error
        if (action != null && action.equals(Action.unknown)) {
          respondError(
              response,
              XMPPError.Condition.bad_request,
              AdHocCommand.SpecificErrorCondition.malformedAction);
          return;
        }

        // If the user didn't specify an action or specify the execute
        // action then follow the actual default execute action
        if (action == null || Action.execute.equals(action)) {
          action = command.getExecuteAction();
        }

        // Check that the specified action was previously
        // offered
        if (!command.isValidAction(action)) {
          respondError(
              response,
              XMPPError.Condition.bad_request,
              AdHocCommand.SpecificErrorCondition.badAction);
          return;
        }

        try {
          // TODO: Check that all the requierd fields of the form are
          // TODO: filled, if not throw an exception. This will simplify the
          // TODO: construction of new commands

          // Since all errors were passed, the response is now a
          // result
          response.setType(IQ.Type.RESULT);

          // Set the new data to the command.
          command.setData(response);

          if (Action.next.equals(action)) {
            command.incrementStage();
            command.next(new Form(requestData.getForm()));
            if (command.isLastStage()) {
              // If it is the last stage then the command is
              // completed
              response.setStatus(Status.completed);
            } else {
              // Otherwise it is still executing
              response.setStatus(Status.executing);
            }
          } else if (Action.complete.equals(action)) {
            command.incrementStage();
            command.complete(new Form(requestData.getForm()));
            response.setStatus(Status.completed);
            // Remove the completed session
            executingCommands.remove(sessionId);
          } else if (Action.prev.equals(action)) {
            command.decrementStage();
            command.prev();
          } else if (Action.cancel.equals(action)) {
            command.cancel();
            response.setStatus(Status.canceled);
            // Remove the canceled session
            executingCommands.remove(sessionId);
          }

          connection.sendPacket(response);
        } catch (XMPPException e) {
          // If there is an exception caused by the next, complete,
          // prev or cancel method, then that error is returned to the
          // requester.
          XMPPError error = e.getXMPPError();

          // If the error type is cancel, then the execution is
          // canceled therefore the status must show that, and the
          // command be removed from the executing list.
          if (XMPPError.Type.CANCEL.equals(error.getType())) {
            response.setStatus(Status.canceled);
            executingCommands.remove(sessionId);
          }
          respondError(response, error);

          e.printStackTrace();
        }
      }
    }
  }