/** @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
      }
    }
  }
}
Exemplo n.º 2
0
 /**
  * Returns the supported features by this XMPP entity.
  *
  * @return an Iterator on the supported features by this XMPP entity.
  */
 public Iterator<String> getFeatures() {
   synchronized (features) {
     return Collections.unmodifiableList(new ArrayList<String>(features)).iterator();
   }
 }
 /**
  * Returns all identities of this client as unmodifiable Collection
  *
  * @return
  */
 public Set<DiscoverInfo.Identity> getIdentities() {
   Set<Identity> res = new HashSet<Identity>(identities);
   // Add the default identity that must exist
   res.add(defaultIdentity);
   return Collections.unmodifiableSet(res);
 }
/**
 * Manages discovery of services in XMPP entities. This class provides:
 *
 * <ol>
 *   <li>A registry of supported features in this XMPP entity.
 *   <li>Automatic response when this XMPP entity is queried for information.
 *   <li>Ability to discover items and information of remote XMPP entities.
 *   <li>Ability to publish publicly available items.
 * </ol>
 *
 * @author Gaston Dombiak
 */
public class ServiceDiscoveryManager {

  private static final String DEFAULT_IDENTITY_NAME = "Smack";
  private static final String DEFAULT_IDENTITY_CATEGORY = "client";
  private static final String DEFAULT_IDENTITY_TYPE = "pc";
  private static DiscoverInfo.Identity defaultIdentity =
      new Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE);

  private Set<DiscoverInfo.Identity> identities = new HashSet<DiscoverInfo.Identity>();
  private DiscoverInfo.Identity identity = defaultIdentity;

  private EntityCapsManager capsManager;

  private static Map<Connection, ServiceDiscoveryManager> instances =
      Collections.synchronizedMap(new WeakHashMap<Connection, ServiceDiscoveryManager>());

  private WeakReference<Connection> connection;
  private final Set<String> features = new HashSet<String>();
  private DataForm extendedInfo = null;
  private Map<String, NodeInformationProvider> nodeInformationProviders =
      new ConcurrentHashMap<String, NodeInformationProvider>();

  // Create a new ServiceDiscoveryManager on every established connection
  static {
    Connection.addConnectionCreationListener(
        new ConnectionCreationListener() {
          public void connectionCreated(Connection connection) {
            getInstanceFor(connection);
          }
        });
  }

  /**
   * Set the default identity all new connections will have. If unchanged the default identity is an
   * identity where category is set to 'client', type is set to 'pc' and name is set to 'Smack'.
   *
   * @param identity
   */
  public static void setDefaultIdentity(DiscoverInfo.Identity identity) {
    defaultIdentity = identity;
  }

  /**
   * Creates a new ServiceDiscoveryManager for a given Connection. This means that the service
   * manager will respond to any service discovery request that the connection may receive.
   *
   * @param connection the connection to which a ServiceDiscoveryManager is going to be created.
   */
  private ServiceDiscoveryManager(Connection connection) {
    this.connection = new WeakReference<Connection>(connection);

    // Register the new instance and associate it with the connection
    instances.put(connection, this);

    addFeature(DiscoverInfo.NAMESPACE);
    addFeature(DiscoverItems.NAMESPACE);

    // Listen for disco#items requests and answer with an empty result
    PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class);
    PacketListener packetListener =
        new PacketListener() {
          public void processPacket(Packet packet) {
            Connection connection = ServiceDiscoveryManager.this.connection.get();
            if (connection == null) return;
            DiscoverItems discoverItems = (DiscoverItems) packet;
            // Send back the items defined in the client if the request is of type GET
            if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) {
              DiscoverItems response = new DiscoverItems();
              response.setType(IQ.Type.RESULT);
              response.setTo(discoverItems.getFrom());
              response.setPacketID(discoverItems.getPacketID());
              response.setNode(discoverItems.getNode());

              // Add the defined items related to the requested node. Look for
              // the NodeInformationProvider associated with the requested node.
              NodeInformationProvider nodeInformationProvider =
                  getNodeInformationProvider(discoverItems.getNode());
              if (nodeInformationProvider != null) {
                // Specified node was found, add node items
                response.addItems(nodeInformationProvider.getNodeItems());
                // Add packet extensions
                response.addExtensions(nodeInformationProvider.getNodePacketExtensions());
              } else if (discoverItems.getNode() != null) {
                // Return <item-not-found/> error since client doesn't contain
                // the specified node
                response.setType(IQ.Type.ERROR);
                response.setError(new XMPPError(XMPPError.Condition.item_not_found));
              }
              connection.sendPacket(response);
            }
          }
        };
    connection.addPacketListener(packetListener, packetFilter);

    // Listen for disco#info requests and answer the client's supported features
    // To add a new feature as supported use the #addFeature message
    packetFilter = new PacketTypeFilter(DiscoverInfo.class);
    packetListener =
        new PacketListener() {
          public void processPacket(Packet packet) {
            Connection connection = ServiceDiscoveryManager.this.connection.get();
            if (connection == null) return;
            DiscoverInfo discoverInfo = (DiscoverInfo) packet;
            // Answer the client's supported features if the request is of the GET type
            if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) {
              DiscoverInfo response = new DiscoverInfo();
              response.setType(IQ.Type.RESULT);
              response.setTo(discoverInfo.getFrom());
              response.setPacketID(discoverInfo.getPacketID());
              response.setNode(discoverInfo.getNode());
              // Add the client's identity and features only if "node" is null
              // and if the request was not send to a node. If Entity Caps are
              // enabled the client's identity and features are may also added
              // if the right node is chosen
              if (discoverInfo.getNode() == null) {
                addDiscoverInfoTo(response);
              } else {
                // Disco#info was sent to a node. Check if we have information of the
                // specified node
                NodeInformationProvider nodeInformationProvider =
                    getNodeInformationProvider(discoverInfo.getNode());
                if (nodeInformationProvider != null) {
                  // Node was found. Add node features
                  response.addFeatures(nodeInformationProvider.getNodeFeatures());
                  // Add node identities
                  response.addIdentities(nodeInformationProvider.getNodeIdentities());
                  // Add packet extensions
                  response.addExtensions(nodeInformationProvider.getNodePacketExtensions());
                } else {
                  // Return <item-not-found/> error since specified node was not found
                  response.setType(IQ.Type.ERROR);
                  response.setError(new XMPPError(XMPPError.Condition.item_not_found));
                }
              }
              connection.sendPacket(response);
            }
          }
        };
    connection.addPacketListener(packetListener, packetFilter);
  }

  /**
   * Returns the name of the client that will be returned when asked for the client identity in a
   * disco request. The name could be any value you need to identity this client.
   *
   * @return the name of the client that will be returned when asked for the client identity in a
   *     disco request.
   */
  public String getIdentityName() {
    return identity.getName();
  }

  /**
   * Sets the name of the client that will be returned when asked for the client identity in a disco
   * request. The name could be any value you need to identity this client.
   *
   * @param name the name of the client that will be returned when asked for the client identity in
   *     a disco request.
   */
  public void setIdentityName(String name) {
    identity.setName(name);
    renewEntityCapsVersion();
  }

  /**
   * Returns the type of client that will be returned when asked for the client identity in a disco
   * request. The valid types are defined by the category client. Follow this link to learn the
   * possible types: <a
   * href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
   *
   * @return the type of client that will be returned when asked for the client identity in a disco
   *     request.
   */
  public String getIdentityType() {
    return identity.getType();
  }

  /**
   * Sets the type of client that will be returned when asked for the client identity in a disco
   * request. The valid types are defined by the category client. Follow this link to learn the
   * possible types: <a
   * href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
   *
   * @param type the type of client that will be returned when asked for the client identity in a
   *     disco request.
   */
  @SuppressWarnings("deprecation")
  public void setIdentityType(String type) {
    identity.setType(type);
    renewEntityCapsVersion();
  }

  /**
   * Add an identity to the client.
   *
   * @param identity
   */
  public void addIdentity(DiscoverInfo.Identity identity) {
    identities.add(identity);
    renewEntityCapsVersion();
  }

  /**
   * Remove an identity from the client. Note that the client needs at least one identity, the
   * default identity, which can not be removed.
   *
   * @param identity
   * @return true, if successful. Otherwise the default identity was given.
   */
  public boolean removeIdentity(DiscoverInfo.Identity identity) {
    if (identity.equals(this.identity)) return false;
    identities.remove(identity);
    renewEntityCapsVersion();
    return true;
  }

  /**
   * Returns all identities of this client as unmodifiable Collection
   *
   * @return
   */
  public Set<DiscoverInfo.Identity> getIdentities() {
    Set<Identity> res = new HashSet<Identity>(identities);
    // Add the default identity that must exist
    res.add(defaultIdentity);
    return Collections.unmodifiableSet(res);
  }

  /**
   * Returns the ServiceDiscoveryManager instance associated with a given Connection.
   *
   * @param connection the connection used to look for the proper ServiceDiscoveryManager.
   * @return the ServiceDiscoveryManager associated with a given Connection.
   */
  public static synchronized ServiceDiscoveryManager getInstanceFor(Connection connection) {
    ServiceDiscoveryManager sdm = instances.get(connection);
    if (sdm == null) {
      sdm = new ServiceDiscoveryManager(connection);
    }
    return sdm;
  }

  /**
   * Add discover info response data.
   *
   * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-basic">XEP-30 Basic Protocol;
   *     Example 2</a>
   * @param response the discover info response packet
   */
  public void addDiscoverInfoTo(DiscoverInfo response) {
    // First add the identities of the connection
    response.addIdentities(getIdentities());

    // Add the registered features to the response
    synchronized (features) {
      for (Iterator<String> it = getFeatures(); it.hasNext(); ) {
        response.addFeature(it.next());
      }
      response.addExtension(extendedInfo);
    }
  }

  /**
   * Returns the NodeInformationProvider responsible for providing information (ie items) related to
   * a given node or <tt>null</null> if none.
   *
   * <p>In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
   * NodeInformationProvider will provide information about the rooms where the user has joined.
   *
   * @param node the node that contains items associated with an entity not addressable as a JID.
   * @return the NodeInformationProvider responsible for providing information related to a given
   *     node.
   */
  private NodeInformationProvider getNodeInformationProvider(String node) {
    if (node == null) {
      return null;
    }
    return nodeInformationProviders.get(node);
  }

  /**
   * Sets the NodeInformationProvider responsible for providing information (ie items) related to a
   * given node. Every time this client receives a disco request regarding the items of a given
   * node, the provider associated to that node will be the responsible for providing the requested
   * information.
   *
   * <p>In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
   * NodeInformationProvider will provide information about the rooms where the user has joined.
   *
   * @param node the node whose items will be provided by the NodeInformationProvider.
   * @param listener the NodeInformationProvider responsible for providing items related to the
   *     node.
   */
  public void setNodeInformationProvider(String node, NodeInformationProvider listener) {
    nodeInformationProviders.put(node, listener);
  }

  /**
   * Removes the NodeInformationProvider responsible for providing information (ie items) related to
   * a given node. This means that no more information will be available for the specified node.
   *
   * <p>In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
   * NodeInformationProvider will provide information about the rooms where the user has joined.
   *
   * @param node the node to remove the associated NodeInformationProvider.
   */
  public void removeNodeInformationProvider(String node) {
    nodeInformationProviders.remove(node);
  }

  /**
   * Returns the supported features by this XMPP entity.
   *
   * @return an Iterator on the supported features by this XMPP entity.
   */
  public Iterator<String> getFeatures() {
    synchronized (features) {
      return Collections.unmodifiableList(new ArrayList<String>(features)).iterator();
    }
  }

  /**
   * Returns the supported features by this XMPP entity.
   *
   * @return a copy of the List on the supported features by this XMPP entity.
   */
  public List<String> getFeaturesList() {
    synchronized (features) {
      return new LinkedList<String>(features);
    }
  }

  /**
   * Registers that a new feature is supported by this XMPP entity. When this client is queried for
   * its information the registered features will be answered.
   *
   * <p>Since no packet is actually sent to the server it is safe to perform this operation before
   * logging to the server. In fact, you may want to configure the supported features before logging
   * to the server so that the information is already available if it is required upon login.
   *
   * @param feature the feature to register as supported.
   */
  public void addFeature(String feature) {
    synchronized (features) {
      features.add(feature);
      renewEntityCapsVersion();
    }
  }

  /**
   * Removes the specified feature from the supported features by this XMPP entity.
   *
   * <p>Since no packet is actually sent to the server it is safe to perform this operation before
   * logging to the server.
   *
   * @param feature the feature to remove from the supported features.
   */
  public void removeFeature(String feature) {
    synchronized (features) {
      features.remove(feature);
      renewEntityCapsVersion();
    }
  }

  /**
   * Returns true if the specified feature is registered in the ServiceDiscoveryManager.
   *
   * @param feature the feature to look for.
   * @return a boolean indicating if the specified featured is registered or not.
   */
  public boolean includesFeature(String feature) {
    synchronized (features) {
      return features.contains(feature);
    }
  }

  /**
   * Registers extended discovery information of this XMPP entity. When this client is queried for
   * its information this data form will be returned as specified by XEP-0128.
   *
   * <p>Since no packet is actually sent to the server it is safe to perform this operation before
   * logging to the server. In fact, you may want to configure the extended info before logging to
   * the server so that the information is already available if it is required upon login.
   *
   * @param info the data form that contains the extend service discovery information.
   */
  public void setExtendedInfo(DataForm info) {
    extendedInfo = info;
    renewEntityCapsVersion();
  }

  /**
   * Returns the data form that is set as extended information for this Service Discovery instance
   * (XEP-0128)
   *
   * @see <a href="http://xmpp.org/extensions/xep-0128.html">XEP-128: Service Discovery
   *     Extensions</a>
   * @return
   */
  public DataForm getExtendedInfo() {
    return extendedInfo;
  }

  /**
   * Returns the data form as List of PacketExtensions, or null if no data form is set. This
   * representation is needed by some classes (e.g. EntityCapsManager, NodeInformationProvider)
   *
   * @return
   */
  public List<PacketExtension> getExtendedInfoAsList() {
    List<PacketExtension> res = null;
    if (extendedInfo != null) {
      res = new ArrayList<PacketExtension>(1);
      res.add(extendedInfo);
    }
    return res;
  }

  /**
   * Removes the data form containing extended service discovery information from the information
   * returned by this XMPP entity.
   *
   * <p>Since no packet is actually sent to the server it is safe to perform this operation before
   * logging to the server.
   */
  public void removeExtendedInfo() {
    extendedInfo = null;
    renewEntityCapsVersion();
  }

  /**
   * Returns the discovered information of a given XMPP entity addressed by its JID. Use null as
   * entityID to query the server
   *
   * @param entityID the address of the XMPP entity or null.
   * @return the discovered information.
   * @throws XMPPException if the operation failed for some reason.
   */
  public DiscoverInfo discoverInfo(String entityID) throws XMPPException {
    if (entityID == null) return discoverInfo(null, null);

    // Check if the have it cached in the Entity Capabilities Manager
    DiscoverInfo info = EntityCapsManager.getDiscoverInfoByUser(entityID);

    if (info != null) {
      // We were able to retrieve the information from Entity Caps and
      // avoided a disco request, hurray!
      return info;
    }

    // Try to get the newest node#version if it's known, otherwise null is
    // returned
    EntityCapsManager.NodeVerHash nvh = EntityCapsManager.getNodeVerHashByJid(entityID);

    // Discover by requesting the information from the remote entity
    // Note that wee need to use NodeVer as argument for Node if it exists
    info = discoverInfo(entityID, nvh != null ? nvh.getNodeVer() : null);

    // If the node version is known, store the new entry.
    if (nvh != null) {
      if (EntityCapsManager.verifyDiscoverInfoVersion(nvh.getVer(), nvh.getHash(), info))
        EntityCapsManager.addDiscoverInfoByNode(nvh.getNodeVer(), info);
    }

    return info;
  }

  /**
   * Returns the discovered information of a given XMPP entity addressed by its JID and note
   * attribute. Use this message only when trying to query information which is not directly
   * addressable.
   *
   * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-basic">XEP-30 Basic Protocol</a>
   * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-nodes">XEP-30 Info Nodes</a>
   * @param entityID the address of the XMPP entity.
   * @param node the optional attribute that supplements the 'jid' attribute.
   * @return the discovered information.
   * @throws XMPPException if the operation failed for some reason.
   */
  public DiscoverInfo discoverInfo(String entityID, String node) throws XMPPException {
    Connection connection = ServiceDiscoveryManager.this.connection.get();
    if (connection == null) throw new XMPPException("Connection instance already gc'ed");

    // Discover the entity's info
    DiscoverInfo disco = new DiscoverInfo();
    disco.setType(IQ.Type.GET);
    disco.setTo(entityID);
    disco.setNode(node);

    Packet result = connection.createPacketCollectorAndSend(disco).nextResultOrThrow();

    return (DiscoverInfo) result;
  }

  /**
   * Returns the discovered items of a given XMPP entity addressed by its JID.
   *
   * @param entityID the address of the XMPP entity.
   * @return the discovered information.
   * @throws XMPPException if the operation failed for some reason.
   */
  public DiscoverItems discoverItems(String entityID) throws XMPPException {
    return discoverItems(entityID, null);
  }

  /**
   * Returns the discovered items of a given XMPP entity addressed by its JID and note attribute.
   * Use this message only when trying to query information which is not directly addressable.
   *
   * @param entityID the address of the XMPP entity.
   * @param node the optional attribute that supplements the 'jid' attribute.
   * @return the discovered items.
   * @throws XMPPException if the operation failed for some reason.
   */
  public DiscoverItems discoverItems(String entityID, String node) throws XMPPException {
    Connection connection = ServiceDiscoveryManager.this.connection.get();
    if (connection == null) throw new XMPPException("Connection instance already gc'ed");

    // Discover the entity's items
    DiscoverItems disco = new DiscoverItems();
    disco.setType(IQ.Type.GET);
    disco.setTo(entityID);
    disco.setNode(node);

    Packet result = connection.createPacketCollectorAndSend(disco).nextResultOrThrow();
    return (DiscoverItems) result;
  }

  /**
   * Returns true if the server supports publishing of items. A client may wish to publish items to
   * the server so that the server can provide items associated to the client. These items will be
   * returned by the server whenever the server receives a disco request targeted to the bare
   * address of the client (i.e. [email protected]).
   *
   * @param entityID the address of the XMPP entity.
   * @return true if the server supports publishing of items.
   * @throws XMPPException if the operation failed for some reason.
   */
  public boolean canPublishItems(String entityID) throws XMPPException {
    DiscoverInfo info = discoverInfo(entityID);
    return canPublishItems(info);
  }

  /**
   * Returns true if the server supports publishing of items. A client may wish to publish items to
   * the server so that the server can provide items associated to the client. These items will be
   * returned by the server whenever the server receives a disco request targeted to the bare
   * address of the client (i.e. [email protected]).
   *
   * @param DiscoverInfo the discover info packet to check.
   * @return true if the server supports publishing of items.
   */
  public static boolean canPublishItems(DiscoverInfo info) {
    return info.containsFeature("http://jabber.org/protocol/disco#publish");
  }

  /**
   * Publishes new items to a parent entity. The item elements to publish MUST have at least a 'jid'
   * attribute specifying the Entity ID of the item, and an action attribute which specifies the
   * action being taken for that item. Possible action values are: "update" and "remove".
   *
   * @param entityID the address of the XMPP entity.
   * @param discoverItems the DiscoveryItems to publish.
   * @throws XMPPException if the operation failed for some reason.
   */
  public void publishItems(String entityID, DiscoverItems discoverItems) throws XMPPException {
    publishItems(entityID, null, discoverItems);
  }

  /**
   * Publishes new items to a parent entity and node. The item elements to publish MUST have at
   * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
   * specifies the action being taken for that item. Possible action values are: "update" and
   * "remove".
   *
   * @param entityID the address of the XMPP entity.
   * @param node the attribute that supplements the 'jid' attribute.
   * @param discoverItems the DiscoveryItems to publish.
   * @throws XMPPException if the operation failed for some reason.
   */
  public void publishItems(String entityID, String node, DiscoverItems discoverItems)
      throws XMPPException {
    Connection connection = ServiceDiscoveryManager.this.connection.get();
    if (connection == null) throw new XMPPException("Connection instance already gc'ed");

    discoverItems.setType(IQ.Type.SET);
    discoverItems.setTo(entityID);
    discoverItems.setNode(node);

    connection.createPacketCollectorAndSend(discoverItems).nextResultOrThrow();
  }

  /** Entity Capabilities */

  /**
   * Loads the ServiceDiscoveryManager with an EntityCapsManger that speeds up certain lookups
   *
   * @param manager
   */
  public void setEntityCapsManager(EntityCapsManager manager) {
    capsManager = manager;
  }

  /** Updates the Entity Capabilities Verification String if EntityCaps is enabled */
  private void renewEntityCapsVersion() {
    if (capsManager != null && capsManager.entityCapsEnabled()) capsManager.updateLocalEntityCaps();
  }
}
Exemplo n.º 5
0
/**
 * XMPP连接管理
 *
 * @author JerSuen
 */
public class XmppManager extends IXmppManager.Stub {

  private XMPPConnection connection;
  private String account, password;
  private ConnectionListener connectionListener;
  private RosterListener rosterListener;
  private IMService imService;
  private PacketListener messageListener;
  private Map<String, Chat> jidChats = Collections.synchronizedMap(new HashMap<String, Chat>());

  public XmppManager(
      ConnectionConfiguration config, String account, String password, IMService imService) {
    this(new XMPPTCPConnection(config), account, password, imService);
  }

  public XmppManager(
      XMPPConnection connection, String account, String password, IMService imService) {
    this.connection = connection;
    this.account = account;
    this.password = password;
    this.imService = imService;
  }

  /** 建立XMPP连接 */
  public boolean connect() throws RemoteException {
    // 已经连接
    if (connection.isConnected()) {
      return true;
    } else {
      try {
        // 开始连接
        connection.connect();
        if (connectionListener == null) {
          // 添加一个连接监听器
          connectionListener = new IMClientConnectListener();
        }
        connection.addConnectionListener(connectionListener);
        return true;
      } catch (SmackException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      } catch (XMPPException e) {
        e.printStackTrace();
      }
    }
    return false;
  }

  /** 登陆XMPP服务器 */
  public boolean login() throws RemoteException {
    // 未建立XMPP连接
    if (!connection.isConnected()) {
      return false;
    }
    // 应经登陆过
    if (connection.isAuthenticated()) {
      return true;
    } else {
      // 开始登陆
      try {
        connection.login(account, password, imService.getString(R.string.app_name));
        if (messageListener == null) {
          messageListener = new MessageListener();
        }
        // 添加消息监听器
        connection.addPacketListener(messageListener, new PacketTypeFilter(Message.class));
        Roster roster = connection.getRoster();
        if (rosterListener == null) {
          rosterListener = new IMClientRosterListener();
        }
        // 添加花名册监听器
        roster.addRosterListener(rosterListener);
        // 获取花名册
        if (roster != null && roster.getEntries().size() > 0) {
          Uri uri = null;
          for (RosterEntry entry : roster.getEntries()) {
            // 获取联系人名片信息
            VCard vCard = new VCard();
            vCard.load(connection, entry.getUser());
            // 用户名称
            String userName = StringUtils.parseName(entry.getUser());
            // 用户备注
            String remarks = entry.getName();
            // 通讯录的名称
            String name = "";
            // 名称与备注判断
            if (userName.equals(remarks) && vCard != null) {
              // 使用联系人的昵称
              name = vCard.getNickName();
            } else {
              // 使用备注
              name = remarks;
            }
            if (vCard != null) {
              IM.saveAvatar(vCard.getAvatar(), StringUtils.parseName(entry.getUser()));
            }
            ContentValues values = new ContentValues();
            values.put(ContactsProvider.ContactColumns.ACCOUNT, entry.getUser());
            values.put(ContactsProvider.ContactColumns.NAME, name);
            String sortStr = PinYin.getPinYin(name);
            values.put(ContactsProvider.ContactColumns.SORT, sortStr);
            values.put(
                ContactsProvider.ContactColumns.SECTION,
                sortStr.substring(0, 1).toUpperCase(Locale.ENGLISH));
            // 储存联系人
            if (imService
                    .getContentResolver()
                    .update(
                        ContactsProvider.CONTACT_URI,
                        values,
                        ContactsProvider.ContactColumns.ACCOUNT + " = ?",
                        new String[] {entry.getUser()})
                == 0) {
              uri = imService.getContentResolver().insert(ContactsProvider.CONTACT_URI, values);
            }
          }
          // 发生改变,通知刷新
          if (uri != null) {
            imService.getContentResolver().notifyChange(uri, null);
          }
        }
      } catch (XMPPException e) {
        e.printStackTrace();
      } catch (SmackException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      }
      return true;
    }
  }

  /** 关闭XMPP连接 */
  public boolean disconnect() throws RemoteException {
    if (connection != null && connection.isConnected()) {
      connection.disconnect();
    }
    return true;
  }

  /** 发送消息 */
  public void sendMessage(String sessionJID, String sessionName, String message, String type)
      throws RemoteException {
    ChatManager chatManager = ChatManager.getInstanceFor(connection);
    Chat chat;
    // 查找Chat对策
    if (jidChats.containsKey(sessionJID)) {
      chat = jidChats.get(sessionJID);
      // 创建Chat
    } else {
      chat = chatManager.createChat(sessionJID, null);
      // 添加到集合
      jidChats.put(sessionJID, chat);
    }

    if (chat != null) {
      try {
        // 发送消息
        chat.sendMessage(message);

        // 保存聊天记录
        ContentValues values = new ContentValues();
        values.put(SMSProvider.SMSColumns.BODY, message);
        values.put(SMSProvider.SMSColumns.TYPE, type);
        values.put(SMSProvider.SMSColumns.TIME, System.currentTimeMillis());

        values.put(SMSProvider.SMSColumns.WHO_ID, IM.getString(IM.ACCOUNT_JID));

        values.put(SMSProvider.SMSColumns.SESSION_ID, sessionJID);
        values.put(SMSProvider.SMSColumns.SESSION_NAME, sessionName);

        imService.getContentResolver().insert(SMSProvider.SMS_URI, values);

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

  /** XMPP连接监听器 */
  private class IMClientConnectListener implements ConnectionListener {

    public void connected(XMPPConnection connection) {}

    public void authenticated(XMPPConnection connection) {}

    public void connectionClosed() {}

    public void connectionClosedOnError(Exception e) {}

    public void reconnectingIn(int seconds) {}

    public void reconnectionSuccessful() {}

    public void reconnectionFailed(Exception e) {}
  }

  /** 花名册监听器 */
  private class IMClientRosterListener implements RosterListener {

    public void entriesAdded(Collection<String> strings) {}

    public void entriesUpdated(Collection<String> strings) {}

    public void entriesDeleted(Collection<String> strings) {}

    public void presenceChanged(Presence presence) {}
  }

  /** 消息监听器 */
  private class MessageListener implements PacketListener {

    public void processPacket(Packet packet) throws SmackException.NotConnectedException {
      if (packet instanceof Message) {
        Message message = (Message) packet;
        // 聊天消息
        if (message.getType() == Message.Type.chat) {
          String whoAccountStr = StringUtils.parseBareAddress(message.getFrom());
          String whoNameStr = "";

          // 查询联系人的名称
          Cursor cursor =
              imService
                  .getContentResolver()
                  .query(
                      ContactsProvider.CONTACT_URI,
                      null,
                      ContactsProvider.ContactColumns.ACCOUNT + " = ?",
                      new String[] {whoAccountStr},
                      null);
          if (cursor != null && cursor.moveToFirst()) {
            cursor.moveToPosition(0);
            whoNameStr =
                cursor.getString(cursor.getColumnIndex(ContactsProvider.ContactColumns.NAME));
          }

          String bodyStr = message.getBody();
          String typeStr = "chat";

          // 插入消息
          ContentValues values = new ContentValues();
          values.put(SMSProvider.SMSColumns.BODY, bodyStr);
          values.put(SMSProvider.SMSColumns.TYPE, typeStr);
          values.put(SMSProvider.SMSColumns.TIME, System.currentTimeMillis());

          values.put(SMSProvider.SMSColumns.WHO_ID, whoAccountStr);

          values.put(SMSProvider.SMSColumns.SESSION_ID, whoAccountStr);
          values.put(SMSProvider.SMSColumns.SESSION_NAME, whoNameStr);

          imService.getContentResolver().insert(SMSProvider.SMS_URI, values);
        }
      }
    }
  }
}