/**
  * 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;
 }
  /**
   * {@inheritDoc}
   *
   * <p>Implements {@link RegistrationStateChangeListener}. Notifies this instance that there has
   * been a change in the <tt>RegistrationState</tt> of {@link #protocolProvider}. Subscribes this
   * instance to {@link ColibriConferenceIQ}s as soon as <tt>protocolProvider</tt> is registered and
   * unsubscribes it as soon as <tt>protocolProvider</tt> is unregistered.
   */
  public void registrationStateChanged(RegistrationStateChangeEvent ev) {
    RegistrationState registrationState = ev.getNewState();

    if (RegistrationState.REGISTERED.equals(registrationState)) {
      protocolProvider.getConnection().addPacketListener(this, this);
    } else if (RegistrationState.UNREGISTERED.equals(registrationState)) {
      XMPPConnection connection = protocolProvider.getConnection();

      if (connection != null) connection.removePacketListener(this);
    }
  }
  /**
   * 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);
  }
  /**
   * Constructor
   *
   * @param provider is the provider that created us
   */
  public OperationSetFileTransferJabberImpl(ProtocolProviderServiceJabberImpl provider) {
    this.jabberProvider = provider;

    provider.addRegistrationStateChangeListener(new RegistrationStateListener());

    // use only ibb for file transfers
    FileTransferNegotiator.IBB_ONLY = true;
  }
  /**
   * 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 (jabberProvider == null)
      throw new IllegalStateException(
          "The provider must be non-null and signed on the "
              + "service before being able to send a file.");
    else if (!jabberProvider.isRegistered()) {
      // if we are not registered but the current status is online
      // change the current status
      if (opSetPersPresence.getPresenceStatus().isOnline()) {
        opSetPersPresence.fireProviderStatusChangeEvent(
            opSetPersPresence.getPresenceStatus(),
            jabberProvider.getJabberStatusEnum().getStatus(JabberStatusEnum.OFFLINE));
      }

      throw new IllegalStateException(
          "The provider must be signed on the service before " + "being able to send a file.");
    }
  }
  /** 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;
    }
  }
  /**
   * 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);
  }
  /**
   * 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;
    }
  }
  /**
   * 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;
  }
  /**
   * Notifies this instance that a specific <tt>ColibriConferenceIQ</tt> has been received.
   *
   * @param conferenceIQ the <tt>ColibriConferenceIQ</tt> which has been received
   */
  private void processColibriConferenceIQ(ColibriConferenceIQ conferenceIQ) {
    /*
     * The application is not a Jitsi Videobridge server, it is a client.
     * Consequently, the specified ColibriConferenceIQ is sent to it in
     * relation to the part of the application's functionality which makes
     * requests to a Jitsi Videobridge server i.e. CallJabberImpl.
     *
     * Additionally, the method processColibriConferenceIQ is presently tasked
     * with processing ColibriConferenceIQ requests only. They are SET IQs
     * sent by the Jitsi Videobridge server to notify the application about
     * updates in the states of (colibri) conferences organized by the
     * application.
     */
    if (IQ.Type.SET.equals(conferenceIQ.getType()) && conferenceIQ.getID() != null) {
      OperationSetBasicTelephony<?> basicTelephony =
          protocolProvider.getOperationSet(OperationSetBasicTelephony.class);

      if (basicTelephony != null) {
        Iterator<? extends Call> i = basicTelephony.getActiveCalls();

        while (i.hasNext()) {
          Call call = i.next();

          if (call instanceof CallJabberImpl) {
            CallJabberImpl callJabberImpl = (CallJabberImpl) call;
            MediaAwareCallConference conference = callJabberImpl.getConference();

            if ((conference != null) && conference.isJitsiVideobridge()) {
              /*
               * TODO We may want to disallow rogue CallJabberImpl
               * instances which may throw an exception to prevent
               * the conferenceIQ from reaching the CallJabberImpl
               * instance which it was meant for.
               */
              if (callJabberImpl.processColibriConferenceIQ(conferenceIQ)) break;
            }
          }
        }
      }
    }
  }
  /**
   * Implements {@link PacketListener}. Notifies this instance that a specific {@link Packet} (which
   * this instance has already expressed interest into by returning <tt>true</tt> from {@link
   * #accept(Packet)}) has been received.
   *
   * @param packet the <tt>Packet</tt> which has been received and which this instance is given a
   *     chance to process
   */
  public void processPacket(Packet packet) {
    /*
     * As we do elsewhere, acknowledge the receipt of the Packet first and
     * then go about our business with it.
     */
    IQ iq = (IQ) packet;

    if (iq.getType() == IQ.Type.SET)
      protocolProvider.getConnection().sendPacket(IQ.createResultIQ(iq));

    /*
     * Now that the acknowledging is out of the way, do go about our
     * business with the Packet.
     */
    ColibriConferenceIQ conferenceIQ = (ColibriConferenceIQ) iq;
    boolean interrupted = false;

    try {
      processColibriConferenceIQ(conferenceIQ);
    } catch (Throwable t) {
      logger.error(
          "An error occurred during the processing of a " + packet.getClass().getName() + " packet",
          t);

      if (t instanceof InterruptedException) {
        /*
         * We cleared the interrupted state of the current Thread by
         * catching the InterruptedException. However, we do not really
         * care whether the current Thread has been interrupted - we
         * caught the InterruptedException because we want to swallow
         * any Throwable. Consequently, we should better restore the
         * interrupted state.
         */
        interrupted = true;
      } else if (t instanceof ThreadDeath) throw (ThreadDeath) t;
    }
    if (interrupted) Thread.currentThread().interrupt();
  }
  /**
   * Sends a file transfer request to the given <tt>toContact</tt>.
   *
   * @return the transfer object
   * @param toContact the contact that should receive the file
   * @param file file to send
   * @param gw special gateway to be used for receiver if its jid misses the domain part
   */
  FileTransfer sendFile(Contact toContact, File file, String gw)
      throws IllegalStateException, IllegalArgumentException, OperationNotSupportedException {
    OutgoingFileTransferJabberImpl outgoingTransfer = null;

    try {
      assertConnected();

      if (file.length() > getMaximumFileLength())
        throw new IllegalArgumentException("File length exceeds the allowed one for this protocol");

      String fullJid = null;
      // Find the jid of the contact which support file transfer
      // and is with highest priority if more than one found
      // if we have equals priorities
      // choose the one that is more available
      OperationSetMultiUserChat mucOpSet =
          jabberProvider.getOperationSet(OperationSetMultiUserChat.class);
      if (mucOpSet != null && mucOpSet.isPrivateMessagingContact(toContact.getAddress())) {
        fullJid = toContact.getAddress();
      } else {
        Iterator<Presence> iter =
            jabberProvider.getConnection().getRoster().getPresences(toContact.getAddress());
        int bestPriority = -1;

        PresenceStatus jabberStatus = null;

        while (iter.hasNext()) {
          Presence presence = iter.next();

          if (jabberProvider.isFeatureListSupported(
              presence.getFrom(),
              new String[] {
                "http://jabber.org/protocol/si",
                "http://jabber.org/protocol/si/profile/file-transfer"
              })) {

            int priority =
                (presence.getPriority() == Integer.MIN_VALUE) ? 0 : presence.getPriority();

            if (priority > bestPriority) {
              bestPriority = priority;
              fullJid = presence.getFrom();
              jabberStatus =
                  OperationSetPersistentPresenceJabberImpl.jabberStatusToPresenceStatus(
                      presence, jabberProvider);
            } else if (priority == bestPriority && jabberStatus != null) {
              PresenceStatus tempStatus =
                  OperationSetPersistentPresenceJabberImpl.jabberStatusToPresenceStatus(
                      presence, jabberProvider);
              if (tempStatus.compareTo(jabberStatus) > 0) {
                fullJid = presence.getFrom();
                jabberStatus = tempStatus;
              }
            }
          }
        }
      }

      // First we check if file transfer is at all supported for this
      // contact.
      if (fullJid == null) {
        throw new OperationNotSupportedException(
            "Contact client or server does not support file transfers.");
      }

      if (gw != null && !fullJid.contains("@") && !fullJid.endsWith(gw)) {
        fullJid = fullJid + "@" + gw;
      }

      OutgoingFileTransfer transfer = manager.createOutgoingFileTransfer(fullJid);

      outgoingTransfer =
          new OutgoingFileTransferJabberImpl(toContact, file, transfer, jabberProvider);

      // Notify all interested listeners that a file transfer has been
      // created.
      FileTransferCreatedEvent event = new FileTransferCreatedEvent(outgoingTransfer, new Date());

      fireFileTransferCreated(event);

      // Send the file through the Jabber file transfer.
      transfer.sendFile(file, "Sending file");

      // Start the status and progress thread.
      new FileTransferProgressThread(transfer, outgoingTransfer).start();
    } catch (XMPPException e) {
      logger.error("Failed to send file.", e);
    }

    return outgoingTransfer;
  }
  /**
   * 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();
  }
  /**
   * Indicates if there's an active video bridge available at this moment. The Jabber provider may
   * announce support for video bridge, but it should not be used for calling until it becomes
   * actually active.
   *
   * @return <tt>true</tt> to indicate that there's currently an active available video bridge,
   *     <tt>false</tt> - otherwise
   */
  public boolean isActive() {
    String jitsiVideobridge = protocolProvider.getJitsiVideobridge();

    return ((jitsiVideobridge != null) && (jitsiVideobridge.length() > 0));
  }
 /**
  * Creates a conference call with the specified callees as call peers via a video bridge provided
  * by the parent Jabber provider.
  *
  * @param callees the list of addresses that we should call
  * @return the newly created conference call containing all CallPeers
  * @throws OperationFailedException if establishing the conference call fails
  * @throws OperationNotSupportedException if the provider does not have any conferencing features.
  */
 public Call createConfCall(String[] callees)
     throws OperationFailedException, OperationNotSupportedException {
   return protocolProvider
       .getOperationSet(OperationSetTelephonyConferencing.class)
       .createConfCall(callees, new MediaAwareCallConference(true));
 }
  /**
   * 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);
  }
 /**
  * Invites the callee represented by the specified uri to an already existing call using a video
  * bridge provided by the parent Jabber provider. The difference between this method and
  * createConfCall is that inviteCalleeToCall allows a user to add new peers to an already
  * established conference.
  *
  * @param uri the callee to invite to an existing conf call.
  * @param call the call that we should invite the callee to.
  * @return the CallPeer object corresponding to the callee represented by the specified uri.
  * @throws OperationFailedException if inviting the specified callee to the specified call fails
  * @throws OperationNotSupportedException if allowing additional callees to a pre-established call
  *     is not supported.
  */
 public CallPeer inviteCalleeToCall(String uri, Call call)
     throws OperationFailedException, OperationNotSupportedException {
   return protocolProvider
       .getOperationSet(OperationSetTelephonyConferencing.class)
       .inviteCalleeToCall(uri, call);
 }