/**
   * 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);
  }
  /**
   * Initializes the packet listeners of the connection that will answer to any service discovery
   * request.
   */
  private void init() {
    // Register the new instance and associate it with the connection
    instances.put(connection, this);
    // Add a listener to the connection that removes the registered instance when
    // the connection is closed
    connection.addConnectionListener(
        new ConnectionListener() {
          public void connectionClosed() {
            // Unregister this instance since the connection has been closed
            instances.remove(connection);
          }

          public void connectionClosedOnError(Exception e) {
            // ignore
          }

          public void reconnectionFailed(Exception e) {
            // ignore
          }

          public void reconnectingIn(int seconds) {
            // ignore
          }

          public void reconnectionSuccessful() {
            // ignore
          }
        });

    // Intercept presence packages and add caps data when inteded.
    // XEP-0115 specifies that a client SHOULD include entity capabilities
    // with every presence notification it sends.
    PacketFilter capsPacketFilter = new PacketTypeFilter(Presence.class);
    PacketInterceptor packetInterceptor =
        new PacketInterceptor() {
          public void interceptPacket(Packet packet) {
            if (capsManager != null) {
              String ver = getEntityCapsVersion();
              CapsExtension caps = new CapsExtension(capsManager.getNode(), ver, "sha-1");
              packet.addExtension(caps);
            }
          }
        };
    connection.addPacketInterceptor(packetInterceptor, capsPacketFilter);

    // 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) {
            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
                List<DiscoverItems.Item> items = nodeInformationProvider.getNodeItems();
                if (items != null) {
                  for (DiscoverItems.Item item : items) {
                    response.addItem(item);
                  }
                }
              } 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) {
            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 if "node" is
              // null or our entity caps version.
              if (discoverInfo.getNode() == null
                  || (capsManager == null
                      ? true
                      : (capsManager.getNode() + "#" + getEntityCapsVersion())
                          .equals(discoverInfo.getNode()))) {
                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
                  List<String> features = nodeInformationProvider.getNodeFeatures();
                  if (features != null) {
                    for (String feature : features) {
                      response.addFeature(feature);
                    }
                  }
                  // Add node identities
                  List<DiscoverInfo.Identity> identities =
                      nodeInformationProvider.getNodeIdentities();
                  if (identities != null) {
                    for (DiscoverInfo.Identity identity : identities) {
                      response.addIdentity(identity);
                    }
                  }
                } 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);
  }
  /**
   * 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);
  }