/** @author Symphorien Wanko */
public class PopupMessageHandlerSLick extends TestSuite implements BundleActivator {
  /** Logger for this class */
  private static Logger logger = Logger.getLogger(PopupMessageHandlerSLick.class);

  /** our bundle context */
  protected static BundleContext bundleContext = null;

  /** implements BundleActivator.start() */
  public void start(BundleContext bc) throws Exception {
    logger.info("starting popup message test ");

    bundleContext = bc;

    setName("PopupMessageHandlerSLick");

    Hashtable<String, String> properties = new Hashtable<String, String>();

    properties.put("service.pid", getName());

    // we maybe are running on machine without WM and systray
    // (test server machine), skip tests
    if (ServiceUtils.getService(bc, SystrayService.class) != null) {
      addTest(TestPopupMessageHandler.suite());
    }

    bundleContext.registerService(getClass().getName(), this, properties);
  }

  /** implements BundleActivator.stop() */
  public void stop(BundleContext bc) throws Exception {}
}
Beispiel #2
0
/**
 * The <tt>Resources</tt> class manages the access to the internationalization properties files and
 * the image resources used in this plugin.
 *
 * @author Yana Stamcheva
 */
public class Resources {

  private static Logger log = Logger.getLogger(Resources.class);

  /** The name of the resource, where internationalization strings for this plugin are stored. */
  private static final String STRING_RESOURCE_NAME =
      "resources.languages.plugin.contactinfo.resources";

  /** The name of the resource, where paths to images used in this bundle are stored. */
  private static final String IMAGE_RESOURCE_NAME =
      "net.java.sip.communicator.plugin.contactinfo.resources";

  /** The string resource bundle. */
  private static final ResourceBundle STRING_RESOURCE_BUNDLE =
      ResourceBundle.getBundle(STRING_RESOURCE_NAME);

  /** The image resource bundle. */
  private static final ResourceBundle IMAGE_RESOURCE_BUNDLE =
      ResourceBundle.getBundle(IMAGE_RESOURCE_NAME);

  /**
   * Returns an internationalized string corresponding to the given key.
   *
   * @param key The key of the string.
   * @return An internationalized string corresponding to the given key.
   */
  public static String getString(String key) {
    try {
      return STRING_RESOURCE_BUNDLE.getString(key);

    } catch (MissingResourceException e) {
      return '!' + key + '!';
    }
  }

  /**
   * Loads an image from a given image identifier.
   *
   * @param imageID The identifier of the image.
   * @return The image for the given identifier.
   */
  public static ImageIcon getImage(String imageID) {
    BufferedImage image = null;

    String path = IMAGE_RESOURCE_BUNDLE.getString(imageID);

    try {
      image = ImageIO.read(Resources.class.getClassLoader().getResourceAsStream(path));
    } catch (IOException e) {
      log.error("Failed to load image:" + path, e);
    }

    return new ImageIcon(image);
  }
}
/**
 * Bundle adds Android specific notification handlers.
 *
 * @author Pawel Domas
 */
public class NotificationActivator implements BundleActivator {
  /** The logger */
  private final Logger logger = Logger.getLogger(NotificationActivator.class);

  /** OSGI bundle context. */
  protected static BundleContext bundleContext;

  /** Notification service instance. */
  private static NotificationService notificationService;

  /** Vibrate handler instance. */
  private VibrateHandlerImpl vibrateHandler;

  /** {@inheritDoc} */
  public void start(BundleContext bc) throws Exception {
    bundleContext = bc;
    try {
      logger.logEntry();
      logger.info("Android notification handler Service...[  STARTED ]");

      // Get the notification service implementation
      ServiceReference notifReference =
          bundleContext.getServiceReference(NotificationService.class.getName());

      notificationService = (NotificationService) bundleContext.getService(notifReference);

      vibrateHandler = new VibrateHandlerImpl();

      notificationService.addActionHandler(vibrateHandler);

      logger.info("Android notification handler Service...[REGISTERED]");
    } finally {
      logger.logExit();
    }
  }

  /** {@inheritDoc} */
  public void stop(BundleContext bc) throws Exception {
    notificationService.removeActionHandler(vibrateHandler.getActionType());

    logger.info("Android notification handler Service ...[STOPPED]");
  }
}
  /**
   * Indicates that the security is time-outed, is not supported by the other end.
   *
   * @param evt Details about the event that caused this message.
   */
  @Override
  public void securityTimeout(CallPeerSecurityTimeoutEvent evt) {
    timer.cancel();

    // fail peer, call
    if (evt.getSource() instanceof AbstractCallPeer) {
      try {
        CallPeer peer = (CallPeer) evt.getSource();
        OperationSetBasicTelephony<?> telephony =
            peer.getProtocolProvider().getOperationSet(OperationSetBasicTelephony.class);

        telephony.hangupCallPeer(
            peer,
            OperationSetBasicTelephony.HANGUP_REASON_ENCRYPTION_REQUIRED,
            "Encryption Required!");
      } catch (OperationFailedException ex) {
        Logger.getLogger(getClass()).error("Failed to hangup peer", ex);
      }
    }
  }
/**
 * An implementation of the <tt>LogMessageNotificationHandler</tt> interface.
 *
 * @author Yana Stamcheva
 */
public class LogMessageNotificationHandlerImpl implements LogMessageNotificationHandler {
  /** The logger that will be used to log messages. */
  private Logger logger = Logger.getLogger(LogMessageNotificationHandlerImpl.class);

  /** {@inheritDoc} */
  public String getActionType() {
    return NotificationAction.ACTION_LOG_MESSAGE;
  }

  /**
   * Logs a message through the sip communicator Logger.
   *
   * @param action the action to act upon
   * @param message the message coming from the event
   */
  public void logMessage(LogMessageNotificationAction action, String message) {
    if (action.getLogType().equals(ERROR_LOG_TYPE)) logger.error(message);
    else if (action.getLogType().equals(INFO_LOG_TYPE)) logger.info(message);
    else if (action.getLogType().equals(TRACE_LOG_TYPE)) logger.trace(message);
  }
}
/**
 * Android implementation of <tt>VerifyCertificateDialogService</tt>.
 *
 * @author Pawel Domas
 */
class CertificateDialogServiceImpl implements VerifyCertificateDialogService {
  /** The logger. */
  private static final Logger logger = Logger.getLogger(CertificateDialogServiceImpl.class);

  /**
   * Maps request ids to <tt>VerifyCertDialog</tt> so that they can be retrieved by Android
   * <tt>Activity</tt> or <tt>Fragments</tt>.
   */
  private Map<Long, VerifyCertDialog> requestMap = new HashMap<Long, VerifyCertDialog>();

  /** {@inheritDoc} */
  @Override
  public VerifyCertificateDialog createDialog(Certificate[] certs, String title, String message) {
    if (title == null)
      title = JitsiApplication.getResString(R.string.service_gui_CERT_DIALOG_TITLE);

    Long requestId = System.currentTimeMillis();

    VerifyCertDialog verifyCertDialog = new VerifyCertDialog(requestId, certs[0], title, message);

    requestMap.put(requestId, verifyCertDialog);

    logger.debug(hashCode() + " creating dialog: " + requestId);

    return verifyCertDialog;
  }

  /**
   * Retrieves the dialog for given <tt>requestId</tt>.
   *
   * @param requestId dialog's request identifier assigned during dialog creation.
   * @return the dialog for given <tt>requestId</tt>.
   */
  public VerifyCertDialog retrieveDialog(Long requestId) {
    logger.debug(hashCode() + " getting dialog: " + requestId);

    return requestMap.get(requestId);
  }
}
/**
 * A wrapper of media quality control.
 *
 * @author Damian Minkov
 */
public class QualityControlWrapper extends AbstractQualityControlWrapper<CallPeerSipImpl> {
  /** Our class logger. */
  private static final Logger logger = Logger.getLogger(QualityControlWrapper.class);

  /**
   * Creates quality control for peer.
   *
   * @param peer peer
   */
  QualityControlWrapper(CallPeerSipImpl peer) {
    super(peer);
  }

  /**
   * Changes the current video settings for the peer with the desired quality settings and inform
   * the peer to stream the video with those settings.
   *
   * @param preset the desired video settings
   * @throws MediaException when the re-invite fails
   */
  @Override
  public void setPreferredRemoteSendMaxPreset(QualityPreset preset) throws MediaException {
    QualityControl qControls = getMediaQualityControl();

    if (qControls != null) {
      qControls.setRemoteSendMaxPreset(preset);
      try {
        // re-invites the peer with the new settings
        peer.sendReInvite();
      } catch (Throwable cause) {
        String message = "Failed to re-invite for video quality change.";

        logger.error(message, cause);

        throw new MediaException(message, MediaException.GENERAL_ERROR, cause);
      }
    }
  }
}
/**
 * Activator for the Metacafe source bundle.
 *
 * @author Purvesh Sahoo
 */
public class MetacafeActivator implements BundleActivator {
  /** The <tt>Logger</tt> used by the <tt>MetacafeActivator</tt> class. */
  private static final Logger logger = Logger.getLogger(MetacafeActivator.class);

  /** The metacafe service registration. */
  private ServiceRegistration metacafeServReg = null;

  /** The source implementation reference. */
  private static ReplacementService metacafeSource = null;

  /**
   * Starts the Metacafe replacement source bundle
   *
   * @param context the <tt>BundleContext</tt> as provided from the OSGi framework
   * @throws Exception if anything goes wrong
   */
  public void start(BundleContext context) throws Exception {
    Hashtable<String, String> hashtable = new Hashtable<String, String>();
    hashtable.put(
        ReplacementService.SOURCE_NAME, ReplacementServiceMetacafeImpl.METACAFE_CONFIG_LABEL);
    metacafeSource = new ReplacementServiceMetacafeImpl();

    metacafeServReg =
        context.registerService(ReplacementService.class.getName(), metacafeSource, hashtable);

    logger.info("Metacafe source implementation [STARTED].");
  }

  /**
   * Unregisters the Metacafe replacement service.
   *
   * @param context BundleContext
   * @throws Exception if anything goes wrong
   */
  public void stop(BundleContext context) throws Exception {
    metacafeServReg.unregister();
    logger.info("Metacafe source implementation [STOPPED].");
  }
}
public class StyledHTMLEditorPane extends JEditorPane {
  private final Logger logger = Logger.getLogger(StyledHTMLEditorPane.class);

  private final HTMLDocument document;

  public StyledHTMLEditorPane() {
    this.setContentType("text/html");

    this.document = (HTMLDocument) this.getDocument();

    this.setDocument(document);

    Constants.loadSimpleStyle(document.getStyleSheet());
  }

  public void appendToEnd(String text) {
    Element root = document.getDefaultRootElement();
    try {
      document.insertAfterEnd(root.getElement(root.getElementCount() - 1), text);
    } catch (BadLocationException e) {
      logger.error("Insert in the HTMLDocument failed.", e);
    } catch (IOException e) {
      logger.error("Insert in the HTMLDocument failed.", e);
    }
  }

  public void insertAfterStart(String text) {
    Element root = this.document.getDefaultRootElement();

    try {
      this.document.insertBeforeStart(root.getElement(0), text);
    } catch (BadLocationException e) {
      logger.error("Insert in the HTMLDocument failed.", e);
    } catch (IOException e) {
      logger.error("Insert in the HTMLDocument failed.", e);
    }
  }
}
Beispiel #10
0
/**
 * Implements a <tt>CandidateHarvester</tt> which gathers <tt>Candidate</tt>s for a specified {@link
 * Component} using Jingle Nodes as defined in XEP 278 "Jingle Relay Nodes".
 *
 * @author Sebastien Vincent
 */
public class JingleNodesHarvester extends AbstractCandidateHarvester {
  /**
   * The <tt>Logger</tt> used by the <tt>JingleNodesHarvester</tt> class and its instances for
   * logging output.
   */
  private static final Logger logger = Logger.getLogger(JingleNodesHarvester.class.getName());

  /** XMPP connection. */
  private SmackServiceNode serviceNode = null;

  /**
   * JingleNodes relay allocate two address/port couple for us. Due to the architecture of Ice4j
   * that harvest address for each component, we store the second address/port couple.
   */
  private TransportAddress localAddressSecond = null;

  /**
   * JingleNodes relay allocate two address/port couple for us. Due to the architecture of Ice4j
   * that harvest address for each component, we store the second address/port couple.
   */
  private TransportAddress relayedAddressSecond = null;

  /**
   * Constructor.
   *
   * @param serviceNode the <tt>SmackServiceNode</tt>
   */
  public JingleNodesHarvester(SmackServiceNode serviceNode) {
    this.serviceNode = serviceNode;
  }

  /**
   * Gathers Jingle Nodes candidates for all host <tt>Candidate</tt>s that are already present in
   * the specified <tt>component</tt>. This method relies on the specified <tt>component</tt> to
   * already contain all its host candidates so that it would resolve them.
   *
   * @param component the {@link Component} that we'd like to gather candidate Jingle Nodes
   *     <tt>Candidate</tt>s for
   * @return the <tt>LocalCandidate</tt>s gathered by this <tt>CandidateHarvester</tt>
   */
  @Override
  public synchronized Collection<LocalCandidate> harvest(Component component) {
    logger.info("harvest Jingle Nodes");

    Collection<LocalCandidate> candidates = new HashSet<LocalCandidate>();
    String ip = null;
    int port = -1;

    /* if we have already a candidate (RTCP) allocated, get it */
    if (localAddressSecond != null && relayedAddressSecond != null) {
      LocalCandidate candidate =
          createJingleNodesCandidate(relayedAddressSecond, component, localAddressSecond);

      // try to add the candidate to the component and then only add it to
      // the harvest not redundant (not sure how it could be red. but ...)
      if (component.addLocalCandidate(candidate)) {
        candidates.add(candidate);
      }

      localAddressSecond = null;
      relayedAddressSecond = null;
      return candidates;
    }

    XMPPConnection conn = serviceNode.getConnection();
    JingleChannelIQ ciq = null;

    if (serviceNode != null) {
      final TrackerEntry preferred = serviceNode.getPreferedRelay();

      if (preferred != null) {
        ciq = SmackServiceNode.getChannel(conn, preferred.getJid());
      }
    }

    if (ciq != null) {
      ip = ciq.getHost();
      port = ciq.getRemoteport();

      if (logger.isInfoEnabled()) {
        logger.info(
            "JN relay: " + ip + " remote port:" + port + " local port: " + ciq.getLocalport());
      }

      if (ip == null || ciq.getRemoteport() == 0) {
        logger.warn("JN relay ignored because ip was null or port 0");
        return candidates;
      }

      // Drop the scope or interface name if the relay sends it
      // along in its IPv6 address. The scope/ifname is only valid on the
      // host that owns the IP and we don't need it here.
      int scopeIndex = ip.indexOf('%');
      if (scopeIndex > 0) {
        logger.warn("Dropping scope from assumed IPv6 address " + ip);
        ip = ip.substring(0, scopeIndex);
      }

      /* RTP */
      TransportAddress relayedAddress = new TransportAddress(ip, port, Transport.UDP);
      TransportAddress localAddress = new TransportAddress(ip, ciq.getLocalport(), Transport.UDP);

      LocalCandidate local = createJingleNodesCandidate(relayedAddress, component, localAddress);

      /* RTCP */
      relayedAddressSecond = new TransportAddress(ip, port + 1, Transport.UDP);
      localAddressSecond = new TransportAddress(ip, ciq.getLocalport() + 1, Transport.UDP);

      // try to add the candidate to the component and then only add it to
      // the harvest not redundant (not sure how it could be red. but ...)
      if (component.addLocalCandidate(local)) {
        candidates.add(local);
      }
    }

    return candidates;
  }

  /**
   * Creates a new <tt>JingleNodesRelayedCandidate</tt> instance which is to represent a specific
   * <tt>TransportAddress</tt>.
   *
   * @param transportAddress the <tt>TransportAddress</tt> allocated by the relay
   * @param component the <tt>Component</tt> for which the candidate will be added
   * @param localEndPoint <tt>TransportAddress</tt> of the Jingle Nodes relay where we will send our
   *     packet.
   * @return a new <tt>JingleNodesRelayedCandidate</tt> instance which represents the specified
   *     <tt>TransportAddress</tt>
   */
  protected JingleNodesCandidate createJingleNodesCandidate(
      TransportAddress transportAddress, Component component, TransportAddress localEndPoint) {
    JingleNodesCandidate cand = null;

    try {
      cand = new JingleNodesCandidate(transportAddress, component, localEndPoint);
      IceSocketWrapper stunSocket = cand.getStunSocket(null);
      cand.getStunStack().addSocket(stunSocket);
    } catch (Throwable e) {
      logger.debug("Exception occurred when creating JingleNodesCandidate: " + e);
    }

    return cand;
  }
}
/**
 * This implementation of the Network Address Manager allows you to intelligently retrieve the
 * address of your localhost according to preferences specified in a number of properties like: <br>
 * net.java.sip.communicator.STUN_SERVER_ADDRESS - the address of the stun server to use for NAT
 * traversal <br>
 * net.java.sip.communicator.STUN_SERVER_PORT - the port of the stun server to use for NAT traversal
 * <br>
 * java.net.preferIPv6Addresses - a system property specifying weather ipv6 addresses are to be
 * preferred in address resolution (default is false for backward compatibility) <br>
 * net.java.sip.communicator.common.PREFERRED_NETWORK_ADDRESS - the address that the user would like
 * to use. (If this is a valid address it will be returned in getLocalhost() calls) <br>
 * net.java.sip.communicator.common.PREFERRED_NETWORK_INTERFACE - the network interface that the
 * user would like to use for fommunication (addresses belonging to that interface will be prefered
 * when selecting a localhost address)
 *
 * @todo further explain the way the service works. explain address selection algorithms and
 *     priorities.
 * @author Emil Ivov
 */
public class NetworkAddressManagerServiceImpl
    implements NetworkAddressManagerService, VetoableChangeListener {
  private static Logger logger = Logger.getLogger(NetworkAddressManagerServiceImpl.class);

  /** The name of the property containing the stun server address. */
  private static final String PROP_STUN_SERVER_ADDRESS =
      "net.java.sip.communicator.impl.netaddr.STUN_SERVER_ADDRESS";
  /** The port number of the stun server to use for NAT traversal */
  private static final String PROP_STUN_SERVER_PORT =
      "net.java.sip.communicator.impl.netaddr.STUN_SERVER_PORT";

  /** A stun4j address resolver */
  private SimpleAddressDetector detector = null;

  /** Specifies whether or not STUN should be used for NAT traversal */
  private boolean useStun = false;

  /** The address of the stun server that we're currently using. */
  private StunAddress stunServerAddress = null;

  /**
   * The socket that we use for dummy connections during selection of a local address that has to be
   * used when communicating with a specific location.
   */
  DatagramSocket localHostFinderSocket = null;

  /**
   * A random (unused)local port to use when trying to select a local host address to use when
   * sending messages to a specific destination.
   */
  private static final int RANDOM_ADDR_DISC_PORT = 55721;

  /**
   * The prefix used for Dynamic Configuration of IPv4 Link-Local Addresses. <br>
   * {@link http://ietf.org/rfc/rfc3927.txt}
   */
  private static final String DYNAMIC_CONF_FOR_IPV4_ADDR_PREFIX = "169.254";

  /**
   * The name of the property containing the number of binds that we should should execute in case a
   * port is already bound to (each retry would be on a new random port).
   */
  public static final String BIND_RETRIES_PROPERTY_NAME =
      "net.java.sip.communicator.service.netaddr.BIND_RETRIES";

  /** Default STUN server address. */
  public static final String DEFAULT_STUN_SERVER_ADDRESS = "stun.iptel.org";

  /** Default STUN server port. */
  public static final int DEFAULT_STUN_SERVER_PORT = 3478;

  /**
   * Initializes this network address manager service implementation and starts all
   * processes/threads associated with this address manager, such as a stun firewall/nat detector,
   * keep alive threads, binding lifetime discovery threads and etc. The method may also be used
   * after a call to stop() as a reinitialization technique.
   */
  public void start() {
    // init stun
    String stunAddressStr = null;
    int port = -1;
    stunAddressStr = NetaddrActivator.getConfigurationService().getString(PROP_STUN_SERVER_ADDRESS);
    String portStr = NetaddrActivator.getConfigurationService().getString(PROP_STUN_SERVER_PORT);

    this.localHostFinderSocket = initRandomPortSocket();

    if (stunAddressStr == null || portStr == null) {
      useStun = false;
      // we use the default stun server address only for chosing a public
      // route and not for stun queries.
      stunServerAddress = new StunAddress(DEFAULT_STUN_SERVER_ADDRESS, DEFAULT_STUN_SERVER_PORT);
      logger.info(
          "Stun server address("
              + stunAddressStr
              + ")/port("
              + portStr
              + ") not set (or invalid). Disabling STUN.");

    } else {
      try {
        port = Integer.valueOf(portStr).intValue();
      } catch (NumberFormatException ex) {
        logger.error(portStr + " is not a valid port number. " + "Defaulting to 3478", ex);
        port = 3478;
      }

      stunServerAddress = new StunAddress(stunAddressStr, port);
      detector = new SimpleAddressDetector(stunServerAddress);

      if (logger.isDebugEnabled()) {
        logger.debug(
            "Created a STUN Address detector for the following "
                + "STUN server: "
                + stunAddressStr
                + ":"
                + port);
      }
      detector.start();
      logger.debug("STUN server detector started;");

      // make sure that someone doesn't set invalid stun address and port
      NetaddrActivator.getConfigurationService()
          .addVetoableChangeListener(PROP_STUN_SERVER_ADDRESS, this);
      NetaddrActivator.getConfigurationService()
          .addVetoableChangeListener(PROP_STUN_SERVER_PORT, this);

      // now start a thread query to the stun server and only set the
      // useStun flag to true if it succeeds.
      launchStunServerTest();
    }
  }

  /**
   * Kills all threads/processes lauched by this thread and prepares it for shutdown. You may use
   * this method as a reinitialization technique ( you'll have to call start afterwards)
   */
  public void stop() {
    try {
      try {
        detector.shutDown();
      } catch (Exception ex) {
        logger.debug("Failed to properly shutdown a stun detector: " + ex.getMessage());
      }
      detector = null;
      useStun = false;

      // remove the listeners
      NetaddrActivator.getConfigurationService()
          .removeVetoableChangeListener(PROP_STUN_SERVER_ADDRESS, this);

      NetaddrActivator.getConfigurationService()
          .removeVetoableChangeListener(PROP_STUN_SERVER_PORT, this);

    } finally {
      logger.logExit();
    }
  }

  /**
   * Returns an InetAddress instance that represents the localhost, and that a socket can bind upon
   * or distribute to peers as a contact address.
   *
   * @param intendedDestination the destination that we'd like to use the localhost address with.
   * @return an InetAddress instance representing the local host, and that a socket can bind upon or
   *     distribute to peers as a contact address.
   */
  public synchronized InetAddress getLocalHost(InetAddress intendedDestination) {
    // no point in making sure that the localHostFinderSocket is initialized.
    // better let it through a NullPointerException.
    InetAddress localHost = null;
    localHostFinderSocket.connect(intendedDestination, this.RANDOM_ADDR_DISC_PORT);
    localHost = localHostFinderSocket.getLocalAddress();
    localHostFinderSocket.disconnect();
    // windows socket implementations return the any address so we need to
    // find something else here ... InetAddress.getLocalHost seems to work
    // better on windows so lets hope it'll do the trick.
    if (localHost.isAnyLocalAddress()) {
      try {
        // all that's inside the if is an ugly IPv6 hack
        // (good ol' IPv6 - always causing more problems than it solves.)
        if (intendedDestination instanceof Inet6Address) {
          // return the first globally routable ipv6 address we find
          // on the machine (and hope it's a good one)
          Enumeration interfaces = NetworkInterface.getNetworkInterfaces();

          while (interfaces.hasMoreElements()) {
            NetworkInterface iface = (NetworkInterface) interfaces.nextElement();
            Enumeration addresses = iface.getInetAddresses();
            while (addresses.hasMoreElements()) {
              InetAddress address = (InetAddress) addresses.nextElement();
              if (address instanceof Inet6Address) {
                if (!address.isAnyLocalAddress()
                    && !address.isLinkLocalAddress()
                    && !address.isSiteLocalAddress()
                    && !address.isLoopbackAddress()) {
                  return address;
                }
              }
            }
          }
        } else localHost = InetAddress.getLocalHost();
        /** @todo test on windows for ipv6 cases */
      } catch (Exception ex) {
        // sigh ... ok return 0.0.0.0
        logger.warn("Failed to get localhost ", ex);
      }
    }

    return localHost;
  }

  /**
   * The method queries a Stun server for a binding for the specified port.
   *
   * @param port the port to resolve (the stun message gets sent trhough that port)
   * @return StunAddress the address returned by the stun server or null if an error occurred or no
   *     address was returned
   * @throws IOException if an error occurs while stun4j is using sockets.
   * @throws BindException if the port is already in use.
   */
  private StunAddress queryStunServer(int port) throws IOException, BindException {
    StunAddress mappedAddress = null;
    if (detector != null && useStun) {
      mappedAddress = detector.getMappingFor(port);
      if (logger.isDebugEnabled())
        logger.debug(
            "For port:"
                + port
                + "a Stun server returned the "
                + "following mapping ["
                + mappedAddress);
    }
    return mappedAddress;
  }

  /**
   * The method queries a Stun server for a binding for the port and address that <tt>sock</tt> is
   * bound on.
   *
   * @param sock the socket whose port and address we'dlike to resolve (the stun message gets sent
   *     trhough that socket)
   * @return StunAddress the address returned by the stun server or null if an error occurred or no
   *     address was returned
   * @throws IOException if an error occurs while stun4j is using sockets.
   * @throws BindException if the port is already in use.
   */
  private StunAddress queryStunServer(DatagramSocket sock) throws IOException, BindException {
    StunAddress mappedAddress = null;
    if (detector != null && useStun) {
      mappedAddress = detector.getMappingFor(sock);
      if (logger.isTraceEnabled()) {
        logger.trace(
            "For socket with address "
                + sock.getLocalAddress().getHostAddress()
                + " and port "
                + sock.getLocalPort()
                + " the stun server returned the "
                + "following mapping ["
                + mappedAddress
                + "]");
      }
    }
    return mappedAddress;
  }

  /**
   * Tries to obtain a mapped/public address for the specified port (possibly by executing a STUN
   * query).
   *
   * @param dst the destination that we'd like to use this address with.
   * @param port the port whose mapping we are interested in.
   * @return a public address corresponding to the specified port or null if all attempts to
   *     retrieve such an address have failed.
   * @throws IOException if an error occurs while stun4j is using sockets.
   * @throws BindException if the port is already in use.
   */
  public InetSocketAddress getPublicAddressFor(InetAddress dst, int port)
      throws IOException, BindException {
    if (!useStun || (dst instanceof Inet6Address)) {
      logger.debug(
          "Stun is disabled for destination "
              + dst
              + ", skipping mapped address recovery (useStun="
              + useStun
              + ", IPv6@="
              + (dst instanceof Inet6Address)
              + ").");
      // we'll still try to bind though so that we could notify the caller
      // if the port has been taken already.
      DatagramSocket bindTestSocket = new DatagramSocket(port);
      bindTestSocket.close();

      // if we're here then the port was free.
      return new InetSocketAddress(getLocalHost(dst), port);
    }
    StunAddress mappedAddress = queryStunServer(port);
    InetSocketAddress result = null;
    if (mappedAddress != null) result = mappedAddress.getSocketAddress();
    else {
      // Apparently STUN failed. Let's try to temporarily disble it
      // and use algorithms in getLocalHost(). ... We should probably
      // eveng think about completely disabling stun, and not only
      // temporarily.
      // Bug report - John J. Barton - IBM
      InetAddress localHost = getLocalHost(dst);
      result = new InetSocketAddress(localHost, port);
    }
    if (logger.isDebugEnabled())
      logger.debug("Returning mapping for port:" + port + " as follows: " + result);
    return result;
  }

  /**
   * Tries to obtain a mapped/public address for the specified port (possibly by executing a STUN
   * query).
   *
   * @param port the port whose mapping we are interested in.
   * @return a public address corresponding to the specified port or null if all attempts to
   *     retrieve such an address have failed.
   * @throws IOException if an error occurs while stun4j is using sockets.
   * @throws BindException if the port is already in use.
   */
  public InetSocketAddress getPublicAddressFor(int port) throws IOException, BindException {
    return getPublicAddressFor(this.stunServerAddress.getSocketAddress().getAddress(), port);
  }

  /**
   * This method gets called when a bound property is changed.
   *
   * @param evt A PropertyChangeEvent object describing the event source and the property that has
   *     changed.
   */
  public void propertyChange(PropertyChangeEvent evt) {
    // there's no point in implementing this method as we have no way of
    // knowing whether the current property change event is the only event
    // we're going to get or whether another one is going to follow..

    // in the case of a STUN_SERVER_ADDRESS property change for example
    // there's no way of knowing whether a STUN_SERVER_PORT property change
    // will follow or not.

    // Reinitializaion will therefore only happen if the reinitialize()
    // method is called.
  }

  /**
   * This method gets called when a property we're interested in is about to change. In case we
   * don't like the new value we throw a PropertyVetoException to prevent the actual change from
   * happening.
   *
   * @param evt a <tt>PropertyChangeEvent</tt> object describing the event source and the property
   *     that will change.
   * @exception PropertyVetoException if we don't want the change to happen.
   */
  public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
    if (evt.getPropertyName().equals(PROP_STUN_SERVER_ADDRESS)) {
      // make sure that we have a valid fqdn or ip address.

      // null or empty port is ok since it implies turning STUN off.
      if (evt.getNewValue() == null) return;

      String host = evt.getNewValue().toString();
      if (host.trim().length() == 0) return;

      boolean ipv6Expected = false;
      if (host.charAt(0) == '[') {
        // This is supposed to be an IPv6 litteral
        if (host.length() > 2 && host.charAt(host.length() - 1) == ']') {
          host = host.substring(1, host.length() - 1);
          ipv6Expected = true;
        } else {
          // This was supposed to be a IPv6 address, but it's not!
          throw new PropertyVetoException("Invalid address string" + host, evt);
        }
      }

      for (int i = 0; i < host.length(); i++) {
        char c = host.charAt(i);
        if (Character.isLetterOrDigit(c)) continue;

        if ((c != '.' && c != ':') || (c == '.' && ipv6Expected) || (c == ':' && !ipv6Expected))
          throw new PropertyVetoException(host + " is not a valid address nor host name", evt);
      }

    } // is prop_stun_server_address
    else if (evt.getPropertyName().equals(PROP_STUN_SERVER_PORT)) {

      // null or empty port is ok since it implies turning STUN off.
      if (evt.getNewValue() == null) return;

      String port = evt.getNewValue().toString();
      if (port.trim().length() == 0) return;

      try {
        Integer.valueOf(evt.getNewValue().toString());
      } catch (NumberFormatException ex) {
        throw new PropertyVetoException(port + " is not a valid port! " + ex.getMessage(), evt);
      }
    }
  }

  /**
   * Initializes and binds a socket that on a random port number. The method would try to bind on a
   * random port and retry 5 times until a free port is found.
   *
   * @return the socket that we have initialized on a randomport number.
   */
  private DatagramSocket initRandomPortSocket() {
    DatagramSocket resultSocket = null;
    String bindRetriesStr =
        NetaddrActivator.getConfigurationService().getString(BIND_RETRIES_PROPERTY_NAME);

    int bindRetries = 5;

    if (bindRetriesStr != null) {
      try {
        bindRetries = Integer.parseInt(bindRetriesStr);
      } catch (NumberFormatException ex) {
        logger.error(
            bindRetriesStr
                + " does not appear to be an integer. "
                + "Defaulting port bind retries to "
                + bindRetries,
            ex);
      }
    }

    int currentlyTriedPort = NetworkUtils.getRandomPortNumber();

    // we'll first try to bind to a random port. if this fails we'll try
    // again (bindRetries times in all) until we find a free local port.
    for (int i = 0; i < bindRetries; i++) {
      try {
        resultSocket = new DatagramSocket(currentlyTriedPort);
        // we succeeded - break so that we don't try to bind again
        break;
      } catch (SocketException exc) {
        if (exc.getMessage().indexOf("Address already in use") == -1) {
          logger.fatal(
              "An exception occurred while trying to create" + "a local host discovery socket.",
              exc);
          resultSocket = null;
          return null;
        }
        // port seems to be taken. try another one.
        logger.debug("Port " + currentlyTriedPort + " seems in use.");
        currentlyTriedPort = NetworkUtils.getRandomPortNumber();
        logger.debug("Retrying bind on port " + currentlyTriedPort);
      }
    }

    return resultSocket;
  }

  /**
   * Runs a test query agains the stun server. If it works we set useStun to true, otherwise we set
   * it to false.
   */
  private void launchStunServerTest() {
    Thread stunServerTestThread =
        new Thread("StunServerTestThread") {
          public void run() {
            DatagramSocket randomSocket = initRandomPortSocket();
            try {
              StunAddress stunAddress = detector.getMappingFor(randomSocket);
              randomSocket.disconnect();
              if (stunAddress != null) {
                useStun = true;
                logger.trace(
                    "StunServer check succeeded for server: "
                        + detector.getServerAddress()
                        + " and local port: "
                        + randomSocket.getLocalPort());
              } else {
                useStun = false;
                logger.trace(
                    "StunServer check failed for server: "
                        + detector.getServerAddress()
                        + " and local port: "
                        + randomSocket.getLocalPort()
                        + ". No address returned by server.");
              }
            } catch (Throwable ex) {
              logger.error(
                  "Failed to run a stun query against " + "server :" + detector.getServerAddress(),
                  ex);
              if (randomSocket.isConnected()) randomSocket.disconnect();
              useStun = false;
            }
          }
        };
    stunServerTestThread.setDaemon(true);
    stunServerTestThread.start();
  }
}
/**
 * Implements a Google Talk <tt>CallPeer</tt>.
 *
 * @author Sebastien Vincent
 * @author Lyubomir Marinov
 */
public class CallPeerGTalkImpl
    extends AbstractCallPeerJabberGTalkImpl<
        CallGTalkImpl, CallPeerMediaHandlerGTalkImpl, SessionIQ> {
  /**
   * The <tt>Logger</tt> used by the <tt>CallPeerGTalkImpl</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(CallPeerGTalkImpl.class);

  /**
   * Returns whether or not the <tt>CallPeer</tt> is an Android phone or a call pass throught Google
   * Voice or uses Google Talk client.
   *
   * <p>We base the detection of the JID's resource which in the case of Android is
   * android/Vtok/Talk.vXXXXXXX (where XXXXXX is a suite of numbers/letters).
   */
  private static boolean isAndroidOrVtokOrTalkClient(String fullJID) {
    int idx = fullJID.indexOf('/');

    if (idx != -1) {
      String res = fullJID.substring(idx + 1);
      if (res.startsWith("android") || res.startsWith("Vtok") || res.startsWith("Talk.v")) {
        return true;
      }
    }

    if (fullJID.contains("@" + ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) return true;

    return false;
  }

  /**
   * Temporary variable for handling client like FreeSwitch that sends "accept" message before
   * sending candidates.
   */
  private SessionIQ sessAcceptedWithNoCands = null;

  /** Session ID. */
  private String sid = null;

  /**
   * Creates a new call peer with address <tt>peerAddress</tt>.
   *
   * @param peerAddress the Google Talk address of the new call peer.
   * @param owningCall the call that contains this call peer.
   */
  public CallPeerGTalkImpl(String peerAddress, CallGTalkImpl owningCall) {
    super(peerAddress, owningCall);

    setMediaHandler(new CallPeerMediaHandlerGTalkImpl(this));
  }

  /**
   * Indicates a user request to answer an incoming call from this <tt>CallPeer</tt>.
   *
   * <p>Sends an OK response to <tt>callPeer</tt>. Make sure that the call peer contains an SDP
   * description when you call this method.
   *
   * @throws OperationFailedException if we fail to create or send the response.
   */
  public synchronized void answer() throws OperationFailedException {
    RtpDescriptionPacketExtension answer = null;

    try {
      getMediaHandler().getTransportManager().wrapupConnectivityEstablishment();
      answer = getMediaHandler().generateSessionAccept(true);
    } catch (IllegalArgumentException e) {
      sessAcceptedWithNoCands = new SessionIQ();

      // HACK apparently FreeSwitch need to have accept before
      answer = getMediaHandler().generateSessionAccept(false);

      SessionIQ response =
          GTalkPacketFactory.createSessionAccept(
              sessionInitIQ.getTo(), sessionInitIQ.getFrom(), getSID(), answer);

      getProtocolProvider().getConnection().sendPacket(response);
      return;
    } catch (Exception exc) {
      logger.info("Failed to answer an incoming call", exc);

      // send an error response
      String reasonText = "Error: " + exc.getMessage();
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.FAILED_APPLICATION,
              reasonText);

      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    SessionIQ response =
        GTalkPacketFactory.createSessionAccept(
            sessionInitIQ.getTo(), sessionInitIQ.getFrom(), getSID(), answer);

    // send the packet first and start the stream later  in case the media
    // relay needs to see it before letting hole punching techniques through.
    if (sessAcceptedWithNoCands == null) getProtocolProvider().getConnection().sendPacket(response);

    try {
      getMediaHandler().start();
    } catch (UndeclaredThrowableException e) {
      Throwable exc = e.getUndeclaredThrowable();

      logger.info("Failed to establish a connection", exc);

      // send an error response
      String reasonText = "Error: " + exc.getMessage();
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.GENERAL_ERROR,
              reasonText);

      getMediaHandler().getTransportManager().close();
      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    // tell everyone we are connecting so that the audio notifications would
    // stop
    setState(CallPeerState.CONNECTED);
  }

  /**
   * Returns the session ID of the Jingle session associated with this call.
   *
   * @return the session ID of the Jingle session associated with this call.
   */
  @Override
  public String getSID() {
    return sessionInitIQ != null ? sessionInitIQ.getID() : sid;
  }

  /**
   * Ends the call with for this <tt>CallPeer</tt>. Depending on the state of the peer the method
   * would send a CANCEL, BYE, or BUSY_HERE message and set the new state to DISCONNECTED.
   *
   * @param failed indicates if the hangup is following to a call failure or simply a disconnect
   * @param reasonText the text, if any, to be set on the <tt>ReasonPacketExtension</tt> as the
   *     value of its
   * @param reasonOtherExtension the <tt>PacketExtension</tt>, if any, to be set on the
   *     <tt>ReasonPacketExtension</tt> as the value of its <tt>otherExtension</tt> property
   */
  public void hangup(boolean failed, String reasonText, PacketExtension reasonOtherExtension) {
    // do nothing if the call is already ended
    if (CallPeerState.DISCONNECTED.equals(getState()) || CallPeerState.FAILED.equals(getState())) {
      if (logger.isDebugEnabled())
        logger.debug("Ignoring a request to hangup a call peer " + "that is already DISCONNECTED");
      return;
    }

    CallPeerState prevPeerState = getState();
    getMediaHandler().getTransportManager().close();

    if (failed) setState(CallPeerState.FAILED, reasonText);
    else setState(CallPeerState.DISCONNECTED, reasonText);

    SessionIQ responseIQ = null;

    if (prevPeerState.equals(CallPeerState.CONNECTED) || CallPeerState.isOnHold(prevPeerState)) {
      responseIQ =
          GTalkPacketFactory.createBye(getProtocolProvider().getOurJID(), peerJID, getSID());
      responseIQ.setInitiator(isInitiator() ? getAddress() : getProtocolProvider().getOurJID());
    } else if (CallPeerState.CONNECTING.equals(prevPeerState)
        || CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(prevPeerState)
        || CallPeerState.ALERTING_REMOTE_SIDE.equals(prevPeerState)) {
      responseIQ =
          GTalkPacketFactory.createCancel(getProtocolProvider().getOurJID(), peerJID, getSID());
      responseIQ.setInitiator(isInitiator() ? getAddress() : getProtocolProvider().getOurJID());
    } else if (prevPeerState.equals(CallPeerState.INCOMING_CALL)) {
      responseIQ =
          GTalkPacketFactory.createBusy(getProtocolProvider().getOurJID(), peerJID, getSID());
      responseIQ.setInitiator(isInitiator() ? getAddress() : getProtocolProvider().getOurJID());
    } else if (prevPeerState.equals(CallPeerState.BUSY)
        || prevPeerState.equals(CallPeerState.FAILED)) {
      // For FAILED and BUSY we only need to update CALL_STATUS
      // as everything else has been done already.
    } else {
      logger.info("Could not determine call peer state!");
    }

    if (responseIQ != null) {
      if (reasonOtherExtension != null) {
        ReasonPacketExtension reason =
            (ReasonPacketExtension)
                responseIQ.getExtension(
                    ReasonPacketExtension.ELEMENT_NAME, ReasonPacketExtension.NAMESPACE);

        if (reason == null) {
          if (reasonOtherExtension instanceof ReasonPacketExtension) {
            responseIQ.setReason((ReasonPacketExtension) reasonOtherExtension);
          }
        } else reason.setOtherExtension(reasonOtherExtension);
      }

      getProtocolProvider().getConnection().sendPacket(responseIQ);
    }
  }

  /**
   * Initiate a Google Talk session {@link SessionIQ}.
   *
   * @param sessionInitiateExtensions a collection of additional and optional
   *     <tt>PacketExtension</tt>s to be added to the <tt>initiate</tt> {@link SessionIQ} which is
   *     to initiate the session with this <tt>CallPeerGTalkImpl</tt>
   * @throws OperationFailedException exception
   */
  protected synchronized void initiateSession(Iterable<PacketExtension> sessionInitiateExtensions)
      throws OperationFailedException {
    sid = SessionIQ.generateSID();
    initiator = false;

    // Create the media description that we'd like to send to the other side.
    RtpDescriptionPacketExtension offer = getMediaHandler().createDescription();

    ProtocolProviderServiceJabberImpl protocolProvider = getProtocolProvider();

    sessionInitIQ =
        GTalkPacketFactory.createSessionInitiate(
            protocolProvider.getOurJID(), this.peerJID, sid, offer);

    if (sessionInitiateExtensions != null) {
      for (PacketExtension sessionInitiateExtension : sessionInitiateExtensions) {
        sessionInitIQ.addExtension(sessionInitiateExtension);
      }
    }

    protocolProvider.getConnection().sendPacket(sessionInitIQ);

    // for Google Voice JID without resource we do not harvest and send
    // candidates
    if (getAddress().endsWith(ProtocolProviderServiceJabberImpl.GOOGLE_VOICE_DOMAIN)) {
      return;
    }

    getMediaHandler()
        .harvestCandidates(
            offer.getPayloadTypes(),
            new CandidatesSender() {
              public void sendCandidates(Iterable<GTalkCandidatePacketExtension> candidates) {
                CallPeerGTalkImpl.this.sendCandidates(candidates);
              }
            });
  }

  /**
   * Process candidates received.
   *
   * @param sessionInitIQ The {@link SessionIQ} that created the session we are handling here
   */
  public void processCandidates(SessionIQ sessionInitIQ) {
    Collection<PacketExtension> extensions = sessionInitIQ.getExtensions();
    List<GTalkCandidatePacketExtension> candidates = new ArrayList<GTalkCandidatePacketExtension>();

    for (PacketExtension ext : extensions) {
      if (ext.getElementName().equalsIgnoreCase(GTalkCandidatePacketExtension.ELEMENT_NAME)) {
        GTalkCandidatePacketExtension cand = (GTalkCandidatePacketExtension) ext;
        candidates.add(cand);
      }
    }

    try {
      getMediaHandler().processCandidates(candidates);
    } catch (OperationFailedException ofe) {
      logger.warn("Failed to process an incoming candidates", ofe);

      // send an error response
      String reasonText = "Error: " + ofe.getMessage();
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.GENERAL_ERROR,
              reasonText);

      getMediaHandler().getTransportManager().close();
      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    // HACK for FreeSwitch that send accept message before sending
    // candidates
    if (sessAcceptedWithNoCands != null) {
      if (isInitiator()) {
        try {
          answer();
        } catch (OperationFailedException e) {
          logger.info("Failed to answer call (FreeSwitch hack)");
        }
      } else {
        final SessionIQ sess = sessAcceptedWithNoCands;
        sessAcceptedWithNoCands = null;

        // run in another thread to not block smack receive thread and
        // possibly delay others candidates messages.
        new Thread() {
          @Override
          public void run() {
            processSessionAccept(sess);
          }
        }.start();
      }
      sessAcceptedWithNoCands = null;
    }
  }

  /**
   * Processes the session initiation {@link SessionIQ} that we were created with, passing its
   * content to the media handler.
   *
   * @param sessionInitIQ The {@link SessionIQ} that created the session that we are handling here.
   */
  public void processSessionAccept(SessionIQ sessionInitIQ) {
    this.sessionInitIQ = sessionInitIQ;

    CallPeerMediaHandlerGTalkImpl mediaHandler = getMediaHandler();
    Collection<PacketExtension> extensions = sessionInitIQ.getExtensions();
    RtpDescriptionPacketExtension answer = null;

    for (PacketExtension ext : extensions) {
      if (ext.getElementName().equalsIgnoreCase(RtpDescriptionPacketExtension.ELEMENT_NAME)) {
        answer = (RtpDescriptionPacketExtension) ext;
        break;
      }
    }

    try {
      mediaHandler.getTransportManager().wrapupConnectivityEstablishment();
      mediaHandler.processAnswer(answer);
    } catch (IllegalArgumentException e) {
      // HACK for FreeSwitch that send accept message before sending
      // candidates
      sessAcceptedWithNoCands = sessionInitIQ;
      return;
    } catch (Exception exc) {
      if (logger.isInfoEnabled()) logger.info("Failed to process a session-accept", exc);

      // send an error response
      String reasonText = "Error: " + exc.getMessage();
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.GENERAL_ERROR,
              reasonText);

      getMediaHandler().getTransportManager().close();
      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    // tell everyone we are connecting so that the audio notifications would
    // stop
    setState(CallPeerState.CONNECTED);

    mediaHandler.start();
  }

  /**
   * Processes the session initiation {@link SessionIQ} that we were created with, passing its
   * content to the media handler and then sends either a "session-info/ringing" or a "terminate"
   * response.
   *
   * @param sessionInitIQ The {@link SessionIQ} that created the session that we are handling here.
   */
  protected synchronized void processSessionInitiate(SessionIQ sessionInitIQ) {
    // Do initiate the session.
    this.sessionInitIQ = sessionInitIQ;
    this.initiator = true;

    RtpDescriptionPacketExtension description = null;

    for (PacketExtension ext : sessionInitIQ.getExtensions()) {
      if (ext.getElementName().equals(RtpDescriptionPacketExtension.ELEMENT_NAME)) {
        description = (RtpDescriptionPacketExtension) ext;
        break;
      }
    }

    if (description == null) {
      logger.info("No description in incoming session initiate");

      // send an error response;
      String reasonText = "Error: no description";
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.INCOMPATIBLE_PARAMETERS,
              reasonText);

      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    try {
      getMediaHandler().processOffer(description);
    } catch (Exception ex) {
      logger.info("Failed to process an incoming session initiate", ex);

      // send an error response;
      String reasonText = "Error: " + ex.getMessage();
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.INCOMPATIBLE_PARAMETERS,
              reasonText);

      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    // If we do not get the info about the remote peer yet. Get it right
    // now.
    if (this.getDiscoveryInfo() == null) {
      String calleeURI = sessionInitIQ.getFrom();
      retrieveDiscoveryInfo(calleeURI);
    }
  }

  /**
   * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a reason to the user, if
   * there is one.
   *
   * @param sessionIQ the {@link SessionIQ} that's terminating our session.
   */
  public void processSessionReject(SessionIQ sessionIQ) {
    processSessionTerminate(sessionIQ);
  }

  /**
   * Puts this peer into a {@link CallPeerState#DISCONNECTED}, indicating a reason to the user, if
   * there is one.
   *
   * @param sessionIQ the {@link SessionIQ} that's terminating our session.
   */
  public void processSessionTerminate(SessionIQ sessionIQ) {
    String reasonStr = "Call ended by remote side.";
    ReasonPacketExtension reasonExt = sessionIQ.getReason();

    if (reasonStr != null && reasonExt != null) {
      Reason reason = reasonExt.getReason();

      if (reason != null) reasonStr += " Reason: " + reason.toString() + ".";

      String text = reasonExt.getText();

      if (text != null) reasonStr += " " + text;
    }

    getMediaHandler().getTransportManager().close();
    setState(CallPeerState.DISCONNECTED, reasonStr);
  }

  /**
   * Sends local candidate addresses from the local peer to the remote peer using the
   * <tt>candidates</tt> {@link SessionIQ}.
   *
   * @param candidates the local candidate addresses to be sent from the local peer to the remote
   *     peer using the <tt>candidates</tt> {@link SessionIQ}
   */
  protected void sendCandidates(Iterable<GTalkCandidatePacketExtension> candidates) {
    ProtocolProviderServiceJabberImpl protocolProvider = getProtocolProvider();

    SessionIQ candidatesIQ = new SessionIQ();

    candidatesIQ.setGTalkType(GTalkType.CANDIDATES);
    candidatesIQ.setFrom(protocolProvider.getOurJID());
    candidatesIQ.setInitiator(isInitiator() ? getAddress() : protocolProvider.getOurJID());
    candidatesIQ.setID(getSID());
    candidatesIQ.setTo(getAddress());
    candidatesIQ.setType(IQ.Type.SET);

    for (GTalkCandidatePacketExtension candidate : candidates) {
      // Android phone and Google Talk client does not seems to like IPv6
      // candidates since it reject the IQ candidates with an error
      // so do not send IPv6 candidates to Android phone or Talk client
      if (isAndroidOrVtokOrTalkClient(getAddress())
          && NetworkUtils.isIPv6Address(candidate.getAddress())) continue;

      candidatesIQ.addExtension(candidate);
    }

    protocolProvider.getConnection().sendPacket(candidatesIQ);
  }

  /** {@inheritDoc} */
  public String getEntity() {
    return getAddress();
  }

  /**
   * {@inheritDoc}
   *
   * <p>Uses the direction of the media stream as a fallback. TODO: return the direction of the
   * GTalk session?
   */
  @Override
  public MediaDirection getDirection(MediaType mediaType) {
    MediaStream stream = getMediaHandler().getStream(mediaType);
    if (stream != null) {
      MediaDirection direction = stream.getDirection();
      return direction == null ? MediaDirection.INACTIVE : direction;
    }

    return MediaDirection.INACTIVE;
  }
}
/**
 * The <tt>NewStatusMessageDialog</tt> is the dialog containing the form for changing the status
 * message for a protocol provider.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public class NewStatusMessageDialog extends SIPCommDialog implements ActionListener, Skinnable {
  /** The Object used for logging. */
  private final Logger logger = Logger.getLogger(NewStatusMessageDialog.class);

  /** The field, containing the status message. */
  private final JTextField messageTextField = new JTextField();

  /** The button, used to cancel this dialog. */
  private final JButton cancelButton =
      new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL"));

  /** The presence operation set through which we change the status message. */
  private final OperationSetPresence presenceOpSet;

  /** Message panel. */
  private JPanel messagePanel;

  /**
   * Creates an instance of <tt>NewStatusMessageDialog</tt>.
   *
   * @param protocolProvider the <tt>ProtocolProviderService</tt>.
   */
  public NewStatusMessageDialog(ProtocolProviderService protocolProvider) {
    presenceOpSet =
        (OperationSetPersistentPresence)
            protocolProvider.getOperationSet(OperationSetPresence.class);

    this.init();
    pack();
  }

  /** Initializes the <tt>NewStatusMessageDialog</tt> by adding the buttons, fields, etc. */
  private void init() {
    JLabel messageLabel =
        new JLabel(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    JPanel dataPanel = new TransparentPanel(new BorderLayout(5, 5));

    JTextArea infoArea =
        new JTextArea(GuiActivator.getResources().getI18NString("service.gui.STATUS_MESSAGE_INFO"));

    JLabel infoTitleLabel =
        new JLabel(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    JPanel labelsPanel = new TransparentPanel(new GridLayout(0, 1));

    JButton okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK"));

    JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));

    this.setTitle(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    this.getRootPane().setDefaultButton(okButton);

    this.setPreferredSize(new Dimension(500, 200));

    infoArea.setEditable(false);
    infoArea.setLineWrap(true);
    infoArea.setWrapStyleWord(true);
    infoArea.setOpaque(false);

    dataPanel.add(messageLabel, BorderLayout.WEST);

    messageTextField.setText(presenceOpSet.getCurrentStatusMessage());
    dataPanel.add(messageTextField, BorderLayout.CENTER);

    infoTitleLabel.setHorizontalAlignment(JLabel.CENTER);
    infoTitleLabel.setFont(infoTitleLabel.getFont().deriveFont(Font.BOLD, 18.0f));

    labelsPanel.add(infoTitleLabel);
    labelsPanel.add(infoArea);
    labelsPanel.add(dataPanel);

    messagePanel = new TransparentPanel(new GridBagLayout());
    GridBagConstraints messagePanelConstraints = new GridBagConstraints();
    messagePanelConstraints.anchor = GridBagConstraints.NORTHWEST;
    messagePanelConstraints.fill = GridBagConstraints.NONE;
    messagePanelConstraints.gridx = 0;
    messagePanelConstraints.gridy = 0;
    messagePanelConstraints.insets = new Insets(5, 0, 5, 10);
    messagePanelConstraints.weightx = 0;
    messagePanelConstraints.weighty = 0;
    messagePanel.add(
        new ImageCanvas(ImageLoader.getImage(ImageLoader.RENAME_DIALOG_ICON)),
        messagePanelConstraints);

    messagePanelConstraints.anchor = GridBagConstraints.NORTH;
    messagePanelConstraints.fill = GridBagConstraints.HORIZONTAL;
    messagePanelConstraints.gridx = 1;
    messagePanelConstraints.insets = new Insets(0, 0, 0, 0);
    messagePanelConstraints.weightx = 1;
    messagePanel.add(labelsPanel, messagePanelConstraints);

    okButton.setName("ok");
    cancelButton.setName("cancel");

    okButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic("service.gui.OK"));
    cancelButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic("service.gui.CANCEL"));

    okButton.addActionListener(this);
    cancelButton.addActionListener(this);

    buttonsPanel.add(okButton);
    buttonsPanel.add(cancelButton);

    JPanel mainPanel = new TransparentPanel(new GridBagLayout());
    mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 10));

    GridBagConstraints mainPanelConstraints = new GridBagConstraints();
    mainPanelConstraints.anchor = GridBagConstraints.NORTH;
    mainPanelConstraints.fill = GridBagConstraints.BOTH;
    mainPanelConstraints.gridx = 0;
    mainPanelConstraints.gridy = 0;
    mainPanelConstraints.weightx = 1;
    mainPanelConstraints.weighty = 1;
    mainPanel.add(messagePanel, mainPanelConstraints);

    mainPanelConstraints.anchor = GridBagConstraints.SOUTHEAST;
    mainPanelConstraints.fill = GridBagConstraints.NONE;
    mainPanelConstraints.gridy = 1;
    mainPanelConstraints.weightx = 0;
    mainPanelConstraints.weighty = 0;
    mainPanel.add(buttonsPanel, mainPanelConstraints);

    this.getContentPane().add(mainPanel);
  }

  /**
   * Handles the <tt>ActionEvent</tt>. In order to change the status message with the new one calls
   * the <tt>PublishStatusMessageThread</tt>.
   *
   * @param e the event that notified us of the action
   */
  public void actionPerformed(ActionEvent e) {
    JButton button = (JButton) e.getSource();
    String name = button.getName();

    if (name.equals("ok")) {
      new PublishStatusMessageThread(messageTextField.getText()).start();
    }

    this.dispose();
  }

  /** Requests the focus in the text field contained in this dialog. */
  public void requestFocusInField() {
    this.messageTextField.requestFocus();
  }

  /** This class allow to use a thread to change the presence status message. */
  private class PublishStatusMessageThread extends Thread {
    private String message;

    private PresenceStatus currentStatus;

    public PublishStatusMessageThread(String message) {
      this.message = message;

      this.currentStatus = presenceOpSet.getPresenceStatus();
    }

    public void run() {
      try {
        presenceOpSet.publishPresenceStatus(currentStatus, message);
      } catch (IllegalArgumentException e1) {

        logger.error("Error - changing status", e1);
      } catch (IllegalStateException e1) {

        logger.error("Error - changing status", e1);
      } catch (OperationFailedException e1) {

        if (e1.getErrorCode() == OperationFailedException.GENERAL_ERROR) {
          logger.error("General error occured while " + "publishing presence status.", e1);
        } else if (e1.getErrorCode() == OperationFailedException.NETWORK_FAILURE) {
          logger.error("Network failure occured while " + "publishing presence status.", e1);
        } else if (e1.getErrorCode() == OperationFailedException.PROVIDER_NOT_REGISTERED) {
          logger.error("Protocol provider must be" + "registered in order to change status.", e1);
        }
      }
    }
  }

  /**
   * Artificially clicks the cancel button when this panel is escaped.
   *
   * @param isEscaped indicates if this dialog is closed by the Esc shortcut
   */
  protected void close(boolean isEscaped) {
    if (isEscaped) cancelButton.doClick();
  }

  /** Reloads icon. */
  public void loadSkin() {
    if (messagePanel != null) {
      for (Component component : messagePanel.getComponents()) {
        if (component instanceof ImageCanvas) {
          ImageCanvas cmp = (ImageCanvas) component;
          cmp.setImage(ImageLoader.getImage(ImageLoader.RENAME_DIALOG_ICON));
        }
      }
    }
  }
}
/**
 * Tests in this class verify whether a precreated contact list is still there and whether it
 * creating contact groups works as expected.
 *
 * @author Emil Ivov
 */
public class TestOperationSetPersistentPresence extends TestCase {
  private static final Logger logger = Logger.getLogger(TestOperationSetPersistentPresence.class);

  private GibberishSlickFixture fixture = new GibberishSlickFixture();
  private OperationSetPersistentPresence opSetPersPresence1 = null;
  private OperationSetPersistentPresence opSetPersPresence2 = null;
  private static final String testGroupName = "NewGroup";
  private static final String testGroupName2 = "Renamed";

  public TestOperationSetPersistentPresence(String name) {
    super(name);
  }

  /**
   * Creates a test suite containing all tests of this class followed by test methods that we want
   * executed in a specified order.
   *
   * @return the Test suite to run
   */
  public static Test suite() {
    TestSuite suite = new TestSuite();

    // the following 2 need to be run in the specified order.
    // (postTestRemoveGroup() needs the group created from
    // postTestCreateGroup() )
    suite.addTest(new TestOperationSetPersistentPresence("postTestCreateGroup"));

    // rename
    suite.addTest(new TestOperationSetPersistentPresence("postTestRenameGroup"));

    suite.addTest(new TestOperationSetPersistentPresence("postTestRemoveGroup"));

    // create the contact list
    suite.addTest(new TestOperationSetPersistentPresence("prepareContactList"));

    suite.addTestSuite(TestOperationSetPersistentPresence.class);

    return suite;
  }

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    fixture.setUp();

    Map<String, OperationSet> supportedOperationSets1 =
        fixture.provider1.getSupportedOperationSets();

    if (supportedOperationSets1 == null || supportedOperationSets1.size() < 1)
      throw new NullPointerException(
          "No OperationSet implementations are supported by " + "this Gibberish implementation. ");

    // get the operation set presence here.
    opSetPersPresence1 =
        (OperationSetPersistentPresence)
            supportedOperationSets1.get(OperationSetPersistentPresence.class.getName());

    // if still null then the implementation doesn't offer a presence
    // operation set which is unacceptable for gibberish.
    if (opSetPersPresence1 == null)
      throw new NullPointerException(
          "An implementation of the gibberish service must provide an "
              + "implementation of at least the one of the Presence "
              + "Operation Sets");

    // lets do it once again for the second provider
    Map<String, OperationSet> supportedOperationSets2 =
        fixture.provider2.getSupportedOperationSets();

    if (supportedOperationSets2 == null || supportedOperationSets2.size() < 1)
      throw new NullPointerException(
          "No OperationSet implementations are supported by " + "this Gibberish implementation. ");

    // get the operation set presence here.
    opSetPersPresence2 =
        (OperationSetPersistentPresence)
            supportedOperationSets2.get(OperationSetPersistentPresence.class.getName());

    // if still null then the implementation doesn't offer a presence
    // operation set which is unacceptable for Gibberish.
    if (opSetPersPresence2 == null)
      throw new NullPointerException(
          "An implementation of the Gibberish service must provide an "
              + "implementation of at least the one of the Presence "
              + "Operation Sets");
  }

  @Override
  protected void tearDown() throws Exception {
    fixture.tearDown();
    super.tearDown();
  }

  /**
   * Retrieves a server stored contact list and checks whether it contains all contacts that have
   * been added there during the initialization phase by the testerAgent.
   */
  public void testRetrievingServerStoredContactList() {
    ContactGroup rootGroup = opSetPersPresence1.getServerStoredContactListRoot();

    logger.debug("=========== Server Stored Contact List =================");

    logger.debug(
        "rootGroup="
            + rootGroup.getGroupName()
            + " rootGroup.childContacts="
            + rootGroup.countContacts()
            + "rootGroup.childGroups="
            + rootGroup.countSubgroups()
            + "Printing rootGroupContents=\n"
            + rootGroup.toString());

    Hashtable<String, List<String>> expectedContactList =
        GibberishSlickFixture.preInstalledBuddyList;

    logger.debug("============== Expected Contact List ===================");
    logger.debug(expectedContactList);

    // Go through the contact list retrieved by the persistence presence set
    // and remove the name of every contact and group that we find there from
    // the expected contct list hashtable.
    Iterator<ContactGroup> groups = rootGroup.subgroups();
    while (groups.hasNext()) {
      ContactGroup group = groups.next();

      List<String> expectedContactsInGroup = expectedContactList.get(group.getGroupName());

      // When sending the offline message
      // the sever creates a group NotInContactList,
      // because the buddy we are sending message to is not in
      // the contactlist. So this group must be ignored
      if (!group.getGroupName().equals("NotInContactList")) {
        assertNotNull(
            "Group "
                + group.getGroupName()
                + " was returned by "
                + "the server but was not in the expected contact list.",
            expectedContactsInGroup);

        Iterator<Contact> contactsIter = group.contacts();
        while (contactsIter.hasNext()) {
          String contactID = contactsIter.next().getAddress();
          expectedContactsInGroup.remove(contactID);
        }

        // If we've removed all the sub contacts, remove the group too.
        if (expectedContactsInGroup.size() == 0) expectedContactList.remove(group.getGroupName());
      }
    }

    // whatever we now have in the expected contact list snapshot are groups,
    // that have been added by the testerAgent but that were not retrieved
    // by the persistent presence operation set.
    assertTrue(
        "The following contacts were on the server sidec contact "
            + "list, but were not returned by the pers. pres. op. set"
            + expectedContactList.toString(),
        expectedContactList.isEmpty());
  }

  /**
   * Creates a group in the server stored contact list, makes sure that the corresponding event has
   * been generated and verifies that the group is in the list.
   *
   * @throws java.lang.Exception
   */
  public void postTestCreateGroup() throws Exception {
    // first clear the list
    fixture.clearProvidersLists();

    Object o = new Object();
    synchronized (o) {
      o.wait(3000);
    }

    logger.trace("testing creation of server stored groups");
    // first add a listener
    GroupChangeCollector groupChangeCollector = new GroupChangeCollector();
    opSetPersPresence1.addServerStoredGroupChangeListener(groupChangeCollector);

    // create the group
    opSetPersPresence1.createServerStoredContactGroup(
        opSetPersPresence1.getServerStoredContactListRoot(), testGroupName);

    groupChangeCollector.waitForEvent(10000);

    opSetPersPresence1.removeServerStoredGroupChangeListener(groupChangeCollector);

    // check whether we got group created event
    assertEquals("Collected Group Change events: ", 1, groupChangeCollector.collectedEvents.size());

    assertEquals(
        "Group name.",
        testGroupName,
        ((ServerStoredGroupEvent) groupChangeCollector.collectedEvents.get(0))
            .getSourceGroup()
            .getGroupName());

    // check whether the group is retrievable
    ContactGroup group =
        opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName);

    assertNotNull("A newly created group was not in the contact list.", group);

    assertEquals("New group name", testGroupName, group.getGroupName());

    // when opearting with groups . the group must have entries
    // so changes to take effect. Otherwise group will be lost after loggingout
    try {
      opSetPersPresence1.subscribe(group, fixture.userID2);

      synchronized (o) {
        o.wait(1500);
      }
    } catch (Exception ex) {
      fail("error adding entry to group : " + group.getGroupName() + " " + ex.getMessage());
    }
  }

  /**
   * Removes the group created in the server stored contact list by the create group test, makes
   * sure that the corresponding event has been generated and verifies that the group is not in the
   * list any more.
   */
  public void postTestRemoveGroup() {
    logger.trace("testing removal of server stored groups");

    // first add a listener
    GroupChangeCollector groupChangeCollector = new GroupChangeCollector();
    opSetPersPresence1.addServerStoredGroupChangeListener(groupChangeCollector);

    try {
      // remove the group
      opSetPersPresence1.removeServerStoredContactGroup(
          opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName2));
    } catch (OperationFailedException ex) {
      logger.error("error removing group", ex);
    }

    groupChangeCollector.waitForEvent(10000);

    opSetPersPresence1.removeServerStoredGroupChangeListener(groupChangeCollector);

    // check whether we got group created event
    assertEquals("Collected Group Change event", 1, groupChangeCollector.collectedEvents.size());

    assertEquals(
        "Group name.",
        testGroupName2,
        ((ServerStoredGroupEvent) groupChangeCollector.collectedEvents.get(0))
            .getSourceGroup()
            .getGroupName());

    // check whether the group is still on the contact list
    ContactGroup group =
        opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName2);

    assertNull("A freshly removed group was still on the contact list.", group);
  }

  /**
   * Renames our test group and checks whether corresponding events are triggered. Verifies whether
   * the group has really changed its name and whether it is findable by its new name. Also makes
   * sure that it does not exist under its previous name any more.
   */
  public void postTestRenameGroup() {
    logger.trace("Testing renaming groups.");

    ContactGroup group =
        opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName);

    // first add a listener
    GroupChangeCollector groupChangeCollector = new GroupChangeCollector();
    opSetPersPresence1.addServerStoredGroupChangeListener(groupChangeCollector);

    // change the name and wait for a confirmation event
    opSetPersPresence1.renameServerStoredContactGroup(group, testGroupName2);

    groupChangeCollector.waitForEvent(10000);

    opSetPersPresence1.removeServerStoredGroupChangeListener(groupChangeCollector);

    // examine the event
    assertEquals("Collected Group Change event", 1, groupChangeCollector.collectedEvents.size());

    assertEquals(
        "Group name.",
        testGroupName2,
        ((ServerStoredGroupEvent) groupChangeCollector.collectedEvents.get(0))
            .getSourceGroup()
            .getGroupName());

    // check whether the group is still on the contact list
    ContactGroup oldGroup =
        opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName);

    assertNull("A group was still findable by its old name after renaming.", oldGroup);

    // make sure that we could find the group by its new name.
    ContactGroup newGroup =
        opSetPersPresence1.getServerStoredContactListRoot().getGroup(testGroupName2);

    assertNotNull("Could not find a renamed group by its new name.", newGroup);
  }

  /**
   * Create the contact list. Later will be test to be sure that creating is ok
   *
   * @throws Exception
   */
  public void prepareContactList() throws Exception {
    fixture.clearProvidersLists();

    Object o = new Object();
    synchronized (o) {
      o.wait(3000);
    }

    String contactList =
        System.getProperty(GibberishProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME, null);

    logger.debug(
        "The "
            + GibberishProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME
            + " property is set to="
            + contactList);

    if (contactList == null || contactList.trim().length() < 6) // at least 4 for a UIN, 1 for the
      // dot and 1 for the grp name
      throw new IllegalArgumentException(
          "The "
              + GibberishProtocolProviderServiceLick.CONTACT_LIST_PROPERTY_NAME
              + " property did not contain a contact list.");
    StringTokenizer tokenizer = new StringTokenizer(contactList, " \n\t");

    logger.debug("tokens contained by the CL tokenized=" + tokenizer.countTokens());

    Hashtable<String, List<String>> contactListToCreate = new Hashtable<String, List<String>>();

    // go over all group.uin tokens
    while (tokenizer.hasMoreTokens()) {
      String groupUinToken = tokenizer.nextToken();
      int dotIndex = groupUinToken.indexOf(".");

      if (dotIndex == -1) {
        throw new IllegalArgumentException(groupUinToken + " is not a valid Group.UIN token");
      }

      String groupName = groupUinToken.substring(0, dotIndex);
      String uin = groupUinToken.substring(dotIndex + 1);

      if (groupName.trim().length() < 1 || uin.trim().length() < 4) {
        throw new IllegalArgumentException(
            groupName + " or " + uin + " are not a valid group name or Gibberish user id.");
      }

      // check if we've already seen this group and if not - add it
      List<String> uinInThisGroup = contactListToCreate.get(groupName);
      if (uinInThisGroup == null) {
        uinInThisGroup = new ArrayList<String>();
        contactListToCreate.put(groupName, uinInThisGroup);
      }

      uinInThisGroup.add(uin);
    }

    // now init the list
    Enumeration<String> newGroupsEnum = contactListToCreate.keys();

    // go over all groups in the contactsToAdd table
    while (newGroupsEnum.hasMoreElements()) {
      String groupName = newGroupsEnum.nextElement();
      logger.debug("Will add group " + groupName);

      opSetPersPresence1.createServerStoredContactGroup(
          opSetPersPresence1.getServerStoredContactListRoot(), groupName);

      ContactGroup newlyCreatedGroup =
          opSetPersPresence1.getServerStoredContactListRoot().getGroup(groupName);

      Iterator<String> contactsToAddToThisGroup = contactListToCreate.get(groupName).iterator();
      while (contactsToAddToThisGroup.hasNext()) {
        String id = contactsToAddToThisGroup.next();

        logger.debug("Will add buddy " + id);
        opSetPersPresence1.subscribe(newlyCreatedGroup, id);
      }
    }

    // store the created contact list for later reference
    GibberishSlickFixture.preInstalledBuddyList = contactListToCreate;
  }

  /**
   * The class would listen for and store received events delivered to
   * <tt>ServerStoredGroupListener</tt>s.
   */
  private class GroupChangeCollector implements ServerStoredGroupListener {
    public ArrayList<EventObject> collectedEvents = new ArrayList<EventObject>();

    /**
     * Blocks until at least one event is received or until waitFor miliseconds pass (whicever
     * happens first).
     *
     * @param waitFor the number of miliseconds that we should be waiting for an event before simply
     *     bailing out.
     */
    public void waitForEvent(long waitFor) {
      synchronized (this) {
        if (collectedEvents.size() > 0) return;

        try {
          wait(waitFor);
        } catch (InterruptedException ex) {
          logger.debug("Interrupted while waiting for a subscription evt", ex);
        }
      }
    }

    /**
     * Called whnever an indication is received that a new server stored group is created.
     *
     * @param evt a ServerStoredGroupChangeEvent containing a reference to the newly created group.
     */
    public void groupCreated(ServerStoredGroupEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }

    /**
     * Called when an indication is received that the name of a server stored contact group has
     * changed.
     *
     * @param evt a ServerStoredGroupChangeEvent containing the details of the name change.
     */
    public void groupNameChanged(ServerStoredGroupEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }

    /**
     * Called whnever an indication is received that an existing server stored group has been
     * removed.
     *
     * @param evt a ServerStoredGroupChangeEvent containing a reference to the newly created group.
     */
    public void groupRemoved(ServerStoredGroupEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }

    /**
     * Called whnever an indication is received that an existing server stored group has been
     * resolved.
     *
     * @param evt a ServerStoredGroupChangeEvent containing a reference to the resolved group.
     */
    public void groupResolved(ServerStoredGroupEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }
  }

  /** The class would listen for and store received subscription modification events. */
  private class SubscriptionEventCollector implements SubscriptionListener {
    public ArrayList<EventObject> collectedEvents = new ArrayList<EventObject>();

    /**
     * Blocks until at least one event is received or until waitFor milliseconds pass (whichever
     * happens first).
     *
     * @param waitFor the number of milliseconds that we should be waiting for an event before
     *     simply bailing out.
     */
    public void waitForEvent(long waitFor) {
      logger.trace("Waiting for a persistent subscription event");

      synchronized (this) {
        if (collectedEvents.size() > 0) {
          logger.trace("SubEvt already received. " + collectedEvents);
          return;
        }

        try {
          wait(waitFor);
          if (collectedEvents.size() > 0) logger.trace("Received a SubEvt in provider status.");
          else logger.trace("No SubEvt received for " + waitFor + "ms.");
        } catch (InterruptedException ex) {
          logger.debug("Interrupted while waiting for a subscription evt", ex);
        }
      }
    }

    /**
     * Stores the received subsctiption and notifies all waiting on this object
     *
     * @param evt the SubscriptionEvent containing the corresponding contact
     */
    public void subscriptionCreated(SubscriptionEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }

    /**
     * Stores the received subsctiption and notifies all waiting on this object
     *
     * @param evt the SubscriptionEvent containing the corresponding contact
     */
    public void subscriptionRemoved(SubscriptionEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }

    /**
     * Stores the received subsctiption and notifies all waiting on this object
     *
     * @param evt the SubscriptionEvent containing the corresponding contact
     */
    public void subscriptionFailed(SubscriptionEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }

    /**
     * Stores the received subsctiption and notifies all waiting on this object
     *
     * @param evt the SubscriptionEvent containing the corresponding contact
     */
    public void subscriptionResolved(SubscriptionEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }

    /**
     * Stores the received subsctiption and notifies all waiting on this object
     *
     * @param evt the SubscriptionEvent containing the corresponding contact
     */
    public void subscriptionMoved(SubscriptionMovedEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }

    /**
     * Stores the received subsctiption and notifies all waiting on this object
     *
     * @param evt the SubscriptionEvent containing the corresponding contact
     */
    public void contactModified(ContactPropertyChangeEvent evt) {
      synchronized (this) {
        logger.debug("Collected evt(" + collectedEvents.size() + ")= " + evt);
        collectedEvents.add(evt);
        notifyAll();
      }
    }
  }
}
/**
 * Activator for the swing notification service.
 *
 * @author Symphorien Wanko
 */
public class SwingNotificationActivator implements BundleActivator {
  /** The bundle context in which we started */
  public static BundleContext bundleContext;

  /** A reference to the configuration service. */
  private static ConfigurationService configService;

  /** Logger for this class. */
  private static final Logger logger = Logger.getLogger(SwingNotificationActivator.class);

  /** A reference to the resource management service. */
  private static ResourceManagementService resourcesService;

  /**
   * Start the swing notification service
   *
   * @param bc
   * @throws java.lang.Exception
   */
  public void start(BundleContext bc) throws Exception {
    if (logger.isInfoEnabled()) logger.info("Swing Notification ...[  STARTING ]");

    bundleContext = bc;

    PopupMessageHandler handler = null;
    handler = new PopupMessageHandlerSwingImpl();

    getConfigurationService();

    bc.registerService(PopupMessageHandler.class.getName(), handler, null);

    if (logger.isInfoEnabled()) logger.info("Swing Notification ...[REGISTERED]");
  }

  public void stop(BundleContext arg0) throws Exception {}

  /**
   * Returns the <tt>ConfigurationService</tt> obtained from the bundle context.
   *
   * @return the <tt>ConfigurationService</tt> obtained from the bundle context
   */
  public static ConfigurationService getConfigurationService() {
    if (configService == null) {
      ServiceReference configReference =
          bundleContext.getServiceReference(ConfigurationService.class.getName());

      configService = (ConfigurationService) bundleContext.getService(configReference);
    }

    return configService;
  }

  /**
   * Returns the <tt>ResourceManagementService</tt> obtained from the bundle context.
   *
   * @return the <tt>ResourceManagementService</tt> obtained from the bundle context
   */
  public static ResourceManagementService getResources() {
    if (resourcesService == null)
      resourcesService = ResourceManagementServiceUtils.getService(bundleContext);
    return resourcesService;
  }
}
/**
 * The <tt>OneToOneCallPeerPanel</tt> is the panel containing data for a call peer in a given call.
 * It contains information like call peer name, photo, call duration, etc.
 *
 * @author Yana Stamcheva
 * @author Lyubomir Marinov
 * @author Sebastien Vincent
 * @author Adam Netocny
 */
public class OneToOneCallPeerPanel extends TransparentPanel
    implements SwingCallPeerRenderer, PropertyChangeListener, Skinnable {
  /**
   * The <tt>Logger</tt> used by the <tt>OneToOneCallPeerPanel</tt> class and its instances for
   * logging output.
   */
  private static final Logger logger = Logger.getLogger(OneToOneCallPeerPanel.class);

  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  /** The <tt>CallPeer</tt>, which is rendered in this panel. */
  private final CallPeer callPeer;

  /** The <tt>Call</tt>, which is rendered in this panel. */
  private final Call call;

  /**
   * The <tt>CallPeerAdapter</tt> which implements common <tt>CallPeer</tt>-related listeners on
   * behalf of this instance.
   */
  private final CallPeerAdapter callPeerAdapter;

  /** The renderer of the call. */
  private final SwingCallRenderer callRenderer;

  /** The component showing the status of the underlying call peer. */
  private final JLabel callStatusLabel = new JLabel();

  /** The center component. */
  private final VideoContainer center;

  /**
   * The AWT <tt>Component</tt> which implements a button which allows closing/hiding the visual
   * <tt>Component</tt> which depicts the video streaming from the local peer/user to the remote
   * peer(s).
   */
  private Component closeLocalVisualComponentButton;

  /**
   * A listener to desktop sharing granted/revoked events and to mouse and keyboard interaction with
   * the remote video displaying the remote desktop.
   */
  private final DesktopSharingMouseAndKeyboardListener desktopSharingMouseAndKeyboardListener;

  /**
   * The indicator which determines whether {@link #dispose()} has already been invoked on this
   * instance. If <tt>true</tt>, this instance is considered non-functional and is to be left to the
   * garbage collector.
   */
  private boolean disposed = false;

  /** The DTMF label. */
  private final JLabel dtmfLabel = new JLabel();

  /** The component responsible for displaying an error message. */
  private JTextComponent errorMessageComponent;

  /** The label showing whether the call is on or off hold. */
  private final JLabel holdStatusLabel = new JLabel();

  /** Sound local level label. */
  private InputVolumeControlButton localLevel;

  /**
   * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to
   * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the local
   * peer/user to the remote peer(s).
   *
   * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the
   * current state of the model of this view.
   */
  private Component localVideo;

  /** The label showing whether the voice has been set to mute. */
  private final JLabel muteStatusLabel = new JLabel();

  /** The <tt>Icon</tt> which represents the avatar of the associated call peer. */
  private ImageIcon peerImage;

  /** The name of the peer. */
  private String peerName;

  /** The label containing the user photo. */
  private final JLabel photoLabel;

  /** Sound remote level label. */
  private Component remoteLevel;

  /**
   * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to
   * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the remote
   * peer(s) to the local peer/user.
   *
   * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the
   * current state of the model of this view.
   */
  private Component remoteVideo;

  /** Current id for security image. */
  private ImageID securityImageID = ImageLoader.SECURE_BUTTON_OFF;

  /** The panel containing security related components. */
  private SecurityPanel<?> securityPanel;

  /** The security status of the peer */
  private final SecurityStatusLabel securityStatusLabel = new SecurityStatusLabel();

  /** The status bar component. */
  private final Component statusBar;

  /** The facility which aids this instance in the dealing with the video-related information. */
  private final UIVideoHandler2 uiVideoHandler;

  /**
   * The <tt>Observer</tt> which listens to changes in the video-related information detected and
   * reported by {@link #uiVideoHandler}.
   */
  private final Observer uiVideoHandlerObserver =
      new Observer() {
        public void update(Observable o, Object arg) {
          updateViewFromModel();
        }
      };

  /**
   * The <tt>Runnable</tt> which is scheduled by {@link #updateViewFromModel()} for execution in the
   * AWT event dispatching thread in order to invoke {@link
   * #updateViewFromModelInEventDispatchThread()}.
   */
  private final Runnable updateViewFromModelInEventDispatchThread =
      new Runnable() {
        public void run() {
          /*
           * We receive events/notifications from various threads and we
           * respond to them in the AWT event dispatching thread. It is
           * possible to first schedule an event to be brought to the AWT
           * event dispatching thread, then to have #dispose() invoked on
           * this instance and, finally, to receive the scheduled event in
           * the AWT event dispatching thread. In such a case, this
           * disposed instance should not respond to the event.
           */
          if (!disposed) updateViewFromModelInEventDispatchThread();
        }
      };

  /**
   * Creates a <tt>CallPeerPanel</tt> for the given call peer.
   *
   * @param callRenderer the renderer of the call
   * @param callPeer the <tt>CallPeer</tt> represented in this panel
   * @param uiVideoHandler the facility which is to aid the new instance in the dealing with the
   *     video-related information
   */
  public OneToOneCallPeerPanel(
      SwingCallRenderer callRenderer, CallPeer callPeer, UIVideoHandler2 uiVideoHandler) {
    this.callRenderer = callRenderer;
    this.callPeer = callPeer;
    // we need to obtain call as soon as possible
    // cause if it fails too quickly we may fail to show it
    this.call = callPeer.getCall();
    this.uiVideoHandler = uiVideoHandler;

    peerName = CallManager.getPeerDisplayName(callPeer);
    securityPanel = SecurityPanel.create(this, callPeer, null);

    photoLabel = new JLabel(getPhotoLabelIcon());
    center = createCenter();
    statusBar = createStatusBar();

    setPeerImage(CallManager.getPeerImage(callPeer));

    /* Lay out the main Components of the UI. */
    setLayout(new GridBagLayout());

    GridBagConstraints cnstrnts = new GridBagConstraints();

    if (center != null) {
      cnstrnts.fill = GridBagConstraints.BOTH;
      cnstrnts.gridx = 0;
      cnstrnts.gridy = 1;
      cnstrnts.weightx = 1;
      cnstrnts.weighty = 1;
      add(center, cnstrnts);
    }
    if (statusBar != null) {
      cnstrnts.fill = GridBagConstraints.NONE;
      cnstrnts.gridx = 0;
      cnstrnts.gridy = 3;
      cnstrnts.weightx = 0;
      cnstrnts.weighty = 0;
      cnstrnts.insets = new Insets(5, 0, 0, 0);
      add(statusBar, cnstrnts);
    }

    createSoundLevelIndicators();
    initSecuritySettings();

    /*
     * Add the listeners which will be notified about changes in the model
     * and which will update this view.
     */
    callPeerAdapter = new CallPeerAdapter(callPeer, this);
    uiVideoHandler.addObserver(uiVideoHandlerObserver);

    /*
     * This view adapts to whether it is displayed in full-screen or
     * windowed mode.
     */
    if (callRenderer instanceof Component) {
      ((Component) callRenderer).addPropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this);
    }

    OperationSetDesktopSharingClient desktopSharingClient =
        callPeer.getProtocolProvider().getOperationSet(OperationSetDesktopSharingClient.class);
    if (desktopSharingClient != null) {
      desktopSharingMouseAndKeyboardListener =
          new DesktopSharingMouseAndKeyboardListener(callPeer, desktopSharingClient);
    } else desktopSharingMouseAndKeyboardListener = null;

    updateViewFromModel();
  }

  /**
   * Creates the <tt>Component</tt> hierarchy of the central area of this <tt>CallPeerPanel</tt>
   * which displays the photo of the <tt>CallPeer</tt> or the video if any.
   */
  private VideoContainer createCenter() {
    photoLabel.setPreferredSize(new Dimension(90, 90));

    return createVideoContainer(photoLabel);
  }

  /** Creates sound level related components. */
  private void createSoundLevelIndicators() {
    TransparentPanel localLevelPanel = new TransparentPanel(new BorderLayout(5, 0));
    TransparentPanel remoteLevelPanel = new TransparentPanel(new BorderLayout(5, 0));

    localLevel =
        new InputVolumeControlButton(
            call, ImageLoader.MICROPHONE, ImageLoader.MUTE_BUTTON, false, false);
    remoteLevel =
        new OutputVolumeControlButton(call.getConference(), ImageLoader.HEADPHONE, false, false)
            .getComponent();

    final SoundLevelIndicator localLevelIndicator =
        new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL);
    final SoundLevelIndicator remoteLevelIndicator =
        new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL);

    localLevelPanel.add(localLevel, BorderLayout.WEST);
    localLevelPanel.add(localLevelIndicator, BorderLayout.CENTER);
    remoteLevelPanel.add(remoteLevel, BorderLayout.WEST);
    remoteLevelPanel.add(remoteLevelIndicator, BorderLayout.CENTER);

    GridBagConstraints constraints = new GridBagConstraints();
    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 0;
    constraints.gridy = 5;
    constraints.weightx = 0;
    constraints.weighty = 0;
    constraints.insets = new Insets(10, 0, 0, 0);

    add(localLevelPanel, constraints);

    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 0;
    constraints.gridy = 6;
    constraints.weightx = 0;
    constraints.weighty = 0;
    constraints.insets = new Insets(5, 0, 10, 0);

    add(remoteLevelPanel, constraints);

    if (!GuiActivator.getConfigurationService()
        .getBoolean(
            "net.java.sip.communicator.impl.gui.main.call." + "DISABLE_SOUND_LEVEL_INDICATORS",
            false)) {
      callPeer.addStreamSoundLevelListener(
          new SoundLevelListener() {
            public void soundLevelChanged(Object source, int level) {
              remoteLevelIndicator.updateSoundLevel(level);
            }
          });
      /*
       * By the time the UI gets to be initialized, the callPeer may have
       * been removed from its Call. As far as the UI is concerned, the
       * callPeer will never have a Call again and there will be no audio
       * levels to display anyway so there is no point in throwing a
       * NullPointerException here.
       */
      if (call != null) {
        call.addLocalUserSoundLevelListener(
            new SoundLevelListener() {
              public void soundLevelChanged(Object source, int level) {
                localLevelIndicator.updateSoundLevel(level);
              }
            });
      }
    }
  }

  /**
   * Creates the <tt>Component</tt> hierarchy of the area of status-related information such as
   * <tt>CallPeer</tt> display name, call duration, security status.
   *
   * @return the root of the <tt>Component</tt> hierarchy of the area of status-related information
   *     such as <tt>CallPeer</tt> display name, call duration, security status
   */
  private Component createStatusBar() {
    // stateLabel
    callStatusLabel.setForeground(Color.WHITE);
    dtmfLabel.setForeground(Color.WHITE);
    callStatusLabel.setText(callPeer.getState().getLocalizedStateString());

    PeerStatusPanel statusPanel = new PeerStatusPanel(new GridBagLayout());

    GridBagConstraints constraints = new GridBagConstraints();

    constraints.gridx = 0;
    constraints.gridy = 0;
    statusPanel.add(securityStatusLabel, constraints);
    initSecurityStatusLabel();

    constraints.gridx++;
    statusPanel.add(holdStatusLabel, constraints);

    constraints.gridx++;
    statusPanel.add(muteStatusLabel, constraints);

    constraints.gridx++;
    callStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 12));
    statusPanel.add(callStatusLabel, constraints);

    constraints.gridx++;
    constraints.weightx = 1f;
    statusPanel.add(dtmfLabel, constraints);

    return statusPanel;
  }

  /**
   * Creates a new AWT <tt>Container</tt> which can display a single <tt>Component</tt> at a time
   * (supposedly, one which represents video) and, in the absence of such a <tt>Component</tt>,
   * displays a predefined default <tt>Component</tt> (in accord with the previous supposition, one
   * which is the default when there is no video). The returned <tt>Container</tt> will track the
   * <tt>Components</tt>s added to and removed from it in order to make sure that
   * <tt>noVideoContainer</tt> is displayed as described.
   *
   * @param noVideoComponent the predefined default <tt>Component</tt> to be displayed in the
   *     returned <tt>Container</tt> when there is no other <tt>Component</tt> in it
   * @return a new <tt>Container</tt> which can display a single <tt>Component</tt> at a time and,
   *     in the absence of such a <tt>Component</tt>, displays <tt>noVideoComponent</tt>
   */
  private VideoContainer createVideoContainer(Component noVideoComponent) {
    Container oldParent = noVideoComponent.getParent();

    if (oldParent != null) oldParent.remove(noVideoComponent);

    return new VideoContainer(noVideoComponent, false);
  }

  /**
   * Releases the resources acquired by this instance which require explicit disposal (e.g. any
   * listeners added to the depicted <tt>CallPeer</tt>. Invoked by <tt>OneToOneCallPanel</tt> when
   * it determines that this <tt>OneToOneCallPeerPanel</tt> is no longer necessary.
   */
  public void dispose() {
    disposed = true;

    callPeerAdapter.dispose();
    uiVideoHandler.deleteObserver(uiVideoHandlerObserver);

    if (callRenderer instanceof Component) {
      ((Component) callRenderer).removePropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this);
    }
  }

  /**
   * Returns the parent <tt>CallPanel</tt> containing this renderer.
   *
   * @return the parent <tt>CallPanel</tt> containing this renderer
   */
  public CallPanel getCallPanel() {
    return callRenderer.getCallContainer();
  }

  /**
   * Returns the parent call renderer.
   *
   * @return the parent call renderer
   */
  public CallRenderer getCallRenderer() {
    return callRenderer;
  }

  /**
   * Returns the component associated with this renderer.
   *
   * @return the component associated with this renderer
   */
  public Component getComponent() {
    return this;
  }

  /**
   * Returns the name of the peer, contained in this panel.
   *
   * @return the name of the peer, contained in this panel
   */
  public String getPeerName() {
    return peerName;
  }

  /**
   * Gets the <tt>Icon</tt> to be displayed in {@link #photoLabel}.
   *
   * @return the <tt>Icon</tt> to be displayed in {@link #photoLabel}
   */
  private ImageIcon getPhotoLabelIcon() {
    return (peerImage == null)
        ? new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))
        : peerImage;
  }

  /** Initializes the security settings for this call peer. */
  private void initSecuritySettings() {
    CallPeerSecurityStatusEvent securityEvent = callPeer.getCurrentSecuritySettings();

    if (securityEvent instanceof CallPeerSecurityOnEvent)
      securityOn((CallPeerSecurityOnEvent) securityEvent);
  }

  /** Initializes the security status label, shown in the call status bar. */
  private void initSecurityStatusLabel() {
    securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));

    securityStatusLabel.addMouseListener(
        new MouseAdapter() {
          /** Invoked when a mouse button has been pressed on a component. */
          @Override
          public void mousePressed(MouseEvent e) {
            // Only show the security details if the security is on.
            SrtpControl ctrl = securityPanel.getSecurityControl();
            if (ctrl instanceof ZrtpControl && ctrl.getSecureCommunicationStatus()) {
              setSecurityPanelVisible(
                  !callRenderer
                      .getCallContainer()
                      .getCallWindow()
                      .getFrame()
                      .getGlassPane()
                      .isVisible());
            }
          }
        });
  }

  /**
   * Determines whether the visual <tt>Component</tt> depicting the video streaming from the local
   * peer/user to the remote peer(s) is currently visible.
   *
   * @return <tt>true</tt> if the visual <tt>Component</tt> depicting the video streaming from the
   *     local peer/user to the remote peer(s) is currently visible; otherwise, <tt>false</tt>
   */
  public boolean isLocalVideoVisible() {
    return uiVideoHandler.isLocalVideoVisible();
  }

  /** Reloads all icons. */
  public void loadSkin() {
    if (localLevel != null)
      localLevel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MICROPHONE)));

    if (remoteLevel != null && remoteLevel instanceof Skinnable)
      ((Skinnable) remoteLevel).loadSkin();

    if (muteStatusLabel.getIcon() != null)
      muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON)));

    if (holdStatusLabel.getIcon() != null)
      holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON)));

    securityStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(securityImageID)));

    if (peerImage == null) {
      photoLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)));
    }
  }

  /**
   * Prints the given DTMG character through this <tt>CallPeerRenderer</tt>.
   *
   * @param dtmfChar the DTMF char to print
   */
  public void printDTMFTone(char dtmfChar) {
    dtmfLabel.setText(dtmfLabel.getText() + dtmfChar);
    if (dtmfLabel.getBorder() == null)
      dtmfLabel.setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 5));
  }

  /**
   * Notifies this instance about a change in the value of a property of a source which of interest
   * to this instance. For example, <tt>OneToOneCallPeerPanel</tt> updates its user
   * interface-related properties upon changes in the value of the {@link
   * CallContainer#PROP_FULL_SCREEN} property of its associated {@link #callRenderer}.
   *
   * @param ev a <tt>PropertyChangeEvent</tt> which identifies the source, the name of the property
   *     and the old and new values
   */
  public void propertyChange(PropertyChangeEvent ev) {
    if (CallContainer.PROP_FULL_SCREEN.equals(ev.getPropertyName())) updateViewFromModel();
  }

  /**
   * Re-dispatches glass pane mouse events only in case they occur on the security panel.
   *
   * @param glassPane the glass pane
   * @param e the mouse event in question
   */
  private void redispatchMouseEvent(Component glassPane, MouseEvent e) {
    Point glassPanePoint = e.getPoint();

    Point securityPanelPoint =
        SwingUtilities.convertPoint(glassPane, glassPanePoint, securityPanel);

    Component component;
    Point componentPoint;

    if (securityPanelPoint.y > 0) {
      component = securityPanel;
      componentPoint = securityPanelPoint;
    } else {
      Container contentPane =
          callRenderer.getCallContainer().getCallWindow().getFrame().getContentPane();

      Point containerPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, contentPane);

      component =
          SwingUtilities.getDeepestComponentAt(contentPane, containerPoint.x, containerPoint.y);

      componentPoint = SwingUtilities.convertPoint(contentPane, glassPanePoint, component);
    }

    if (component != null)
      component.dispatchEvent(
          new MouseEvent(
              component,
              e.getID(),
              e.getWhen(),
              e.getModifiers(),
              componentPoint.x,
              componentPoint.y,
              e.getClickCount(),
              e.isPopupTrigger()));

    e.consume();
  }

  /**
   * The handler for the security event received. The security event for starting establish a secure
   * connection.
   *
   * @param evt the security started event received
   */
  public void securityNegotiationStarted(CallPeerSecurityNegotiationStartedEvent evt) {
    if (Boolean.parseBoolean(
        GuiActivator.getResources().getSettingsString("impl.gui.PARANOIA_UI"))) {
      SrtpControl srtpControl = null;
      if (callPeer instanceof MediaAwareCallPeer) srtpControl = evt.getSecurityController();

      securityPanel = new ParanoiaTimerSecurityPanel<SrtpControl>(srtpControl);

      setSecurityPanelVisible(true);
    }
  }

  /**
   * Indicates that the security has gone off.
   *
   * @param evt the <tt>CallPeerSecurityOffEvent</tt> that notified us
   */
  public void securityOff(final CallPeerSecurityOffEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityOff(evt);
            }
          });
      return;
    }

    if (evt.getSessionType() == CallPeerSecurityOffEvent.AUDIO_SESSION) {
      securityStatusLabel.setText("");
      securityStatusLabel.setSecurityOff();
      if (securityStatusLabel.getBorder() == null)
        securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 3));
    }

    securityPanel.securityOff(evt);
  }

  /**
   * Indicates that the security is turned on.
   *
   * <p>Sets the secured status icon to the status panel and initializes/updates the corresponding
   * security details.
   *
   * @param evt Details about the event that caused this message.
   */
  public void securityOn(final CallPeerSecurityOnEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityOn(evt);
            }
          });
      return;
    }

    // If the securityOn is called without a specific event, we'll just set
    // the security label status to on.
    if (evt == null) {
      securityStatusLabel.setSecurityOn();
      return;
    }

    SrtpControl srtpControl = evt.getSecurityController();

    if (!srtpControl.requiresSecureSignalingTransport()
        || callPeer.getProtocolProvider().isSignalingTransportSecure()) {
      if (srtpControl instanceof ZrtpControl) {
        securityStatusLabel.setText("zrtp");

        if (!((ZrtpControl) srtpControl).isSecurityVerified())
          securityStatusLabel.setSecurityPending();
        else securityStatusLabel.setSecurityOn();
      } else securityStatusLabel.setSecurityOn();
    }

    // if we have some other panel, using other control
    if (!srtpControl.getClass().isInstance(securityPanel.getSecurityControl())
        || (securityPanel instanceof ParanoiaTimerSecurityPanel)) {
      setSecurityPanelVisible(false);

      securityPanel = SecurityPanel.create(this, callPeer, srtpControl);

      if (srtpControl instanceof ZrtpControl)
        ((ZrtpSecurityPanel) securityPanel).setSecurityStatusLabel(securityStatusLabel);
    }

    securityPanel.securityOn(evt);

    boolean isSecurityLowPriority =
        Boolean.parseBoolean(
            GuiActivator.getResources()
                .getSettingsString("impl.gui.I_DONT_CARE_THAT_MUCH_ABOUT_SECURITY"));

    // Display ZRTP panel in case SAS was not verified or a AOR mismtach
    // was detected during creation of ZrtpSecurityPanel.
    // Don't show panel if user does not care about security at all.
    if (srtpControl instanceof ZrtpControl
        && !isSecurityLowPriority
        && (!((ZrtpControl) srtpControl).isSecurityVerified()
            || ((ZrtpSecurityPanel) securityPanel).isZidAorMismatch())) {
      setSecurityPanelVisible(true);
    }

    this.revalidate();
  }

  /** Indicates that the security status is pending confirmation. */
  public void securityPending() {
    securityStatusLabel.setSecurityPending();
  }

  /**
   * Indicates that the security is timeouted, is not supported by the other end.
   *
   * @param evt Details about the event that caused this message.
   */
  public void securityTimeout(final CallPeerSecurityTimeoutEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityTimeout(evt);
            }
          });
      return;
    }

    if (securityPanel != null) securityPanel.securityTimeout(evt);
  }

  /**
   * Sets the reason of a call failure if one occurs. The renderer should display this reason to the
   * user.
   *
   * @param reason the reason to display
   */
  public void setErrorReason(final String reason) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setErrorReason(reason);
            }
          });
      return;
    }

    if (errorMessageComponent == null) {
      errorMessageComponent = new JTextPane();

      JTextPane textPane = (JTextPane) errorMessageComponent;
      textPane.setEditable(false);
      textPane.setOpaque(false);

      StyledDocument doc = textPane.getStyledDocument();

      MutableAttributeSet standard = new SimpleAttributeSet();
      StyleConstants.setAlignment(standard, StyleConstants.ALIGN_CENTER);
      StyleConstants.setFontFamily(standard, textPane.getFont().getFamily());
      StyleConstants.setFontSize(standard, 12);
      doc.setParagraphAttributes(0, 0, standard, true);

      GridBagConstraints constraints = new GridBagConstraints();
      constraints.fill = GridBagConstraints.HORIZONTAL;
      constraints.gridx = 0;
      constraints.gridy = 4;
      constraints.weightx = 1;
      constraints.weighty = 0;
      constraints.insets = new Insets(5, 0, 0, 0);

      add(errorMessageComponent, constraints);
      this.revalidate();
    }

    errorMessageComponent.setText(reason);

    if (isVisible()) errorMessageComponent.repaint();
  }

  /**
   * Shows/hides the visual <tt>Component</tt> depicting the video streaming from the local
   * peer/user to the remote peer(s).
   *
   * @param visible <tt>true</tt> to show the visual <tt>Component</tt> depicting the video
   *     streaming from the local peer/user to the remote peer(s); <tt>false</tt>, otherwise
   */
  public void setLocalVideoVisible(boolean visible) {
    uiVideoHandler.setLocalVideoVisible(visible);
  }

  /**
   * Sets the mute status icon to the status panel.
   *
   * @param isMute indicates if the call with this peer is muted
   */
  public void setMute(final boolean isMute) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setMute(isMute);
            }
          });
      return;
    }

    if (isMute) {
      muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON)));
      muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3));
    } else {
      muteStatusLabel.setIcon(null);
      muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
    }

    // Update input volume control button state to reflect the current
    // mute status.
    if (localLevel.isSelected() != isMute) localLevel.setSelected(isMute);

    this.revalidate();
    this.repaint();
  }

  /**
   * Sets the "on hold" property value.
   *
   * @param isOnHold indicates if the call with this peer is put on hold
   */
  public void setOnHold(final boolean isOnHold) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setOnHold(isOnHold);
            }
          });
      return;
    }

    if (isOnHold) {
      holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON)));
      holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3));
    } else {
      holdStatusLabel.setIcon(null);
      holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
    }

    this.revalidate();
    this.repaint();
  }

  /**
   * Set the image of the peer
   *
   * @param image new image
   */
  public void setPeerImage(byte[] image) {
    // If the image is still null we try to obtain it from one of the
    // available contact sources.
    if (image == null || image.length <= 0) {
      GuiActivator.getContactList().setSourceContactImage(peerName, photoLabel, 100, 100);
    } else {
      peerImage = ImageUtils.getScaledRoundedIcon(image, 100, 100);
      if (peerImage == null) peerImage = getPhotoLabelIcon();

      if (!SwingUtilities.isEventDispatchThread()) {
        SwingUtilities.invokeLater(
            new Runnable() {
              public void run() {
                photoLabel.setIcon(peerImage);
                photoLabel.repaint();
              }
            });
      } else {
        photoLabel.setIcon(peerImage);
        photoLabel.repaint();
      }
    }
  }

  /**
   * Sets the name of the peer.
   *
   * @param name the name of the peer
   */
  public void setPeerName(final String name) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setPeerName(name);
            }
          });
      return;
    }

    peerName = name;

    ((OneToOneCallPanel) callRenderer).setPeerName(name);
  }

  /**
   * Sets the state of the contained call peer by specifying the state name.
   *
   * @param oldState the previous state of the peer
   * @param newState the new state of the peer
   * @param stateString the state of the contained call peer
   */
  public void setPeerState(
      final CallPeerState oldState, final CallPeerState newState, final String stateString) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setPeerState(oldState, newState, stateString);
            }
          });
      return;
    }

    this.callStatusLabel.setText(stateString);

    if (newState == CallPeerState.CONNECTED
        && !CallPeerState.isOnHold(oldState)
        && !securityStatusLabel.isSecurityStatusSet()) {
      securityStatusLabel.setSecurityOff();
    }
  }

  /**
   * Shows/hides the security panel.
   *
   * @param isVisible <tt>true</tt> to show the security panel, <tt>false</tt> to hide it
   */
  public void setSecurityPanelVisible(final boolean isVisible) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setSecurityPanelVisible(isVisible);
            }
          });
      return;
    }

    final JFrame callFrame = callRenderer.getCallContainer().getCallWindow().getFrame();

    final JPanel glassPane = (JPanel) callFrame.getGlassPane();

    if (!isVisible) {
      // Need to hide the security panel explicitly in order to keep the
      // fade effect.
      securityPanel.setVisible(false);
      glassPane.setVisible(false);
      glassPane.removeAll();
    } else {
      glassPane.setLayout(null);
      glassPane.addMouseListener(
          new MouseListener() {
            public void mouseClicked(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseEntered(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseExited(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mousePressed(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseReleased(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }
          });

      Point securityLabelPoint = securityStatusLabel.getLocation();

      Point newPoint =
          SwingUtilities.convertPoint(
              securityStatusLabel.getParent(),
              securityLabelPoint.x,
              securityLabelPoint.y,
              callFrame);

      securityPanel.setBeginPoint(new Point((int) newPoint.getX() + 15, 0));
      securityPanel.setBounds(0, (int) newPoint.getY() - 5, this.getWidth(), 130);

      glassPane.add(securityPanel);
      // Need to show the security panel explicitly in order to keep the
      // fade effect.
      securityPanel.setVisible(true);
      glassPane.setVisible(true);

      glassPane.addComponentListener(
          new ComponentAdapter() {
            /** Invoked when the component's size changes. */
            @Override
            public void componentResized(ComponentEvent e) {
              if (glassPane.isVisible()) {
                glassPane.setVisible(false);
                callFrame.removeComponentListener(this);
              }
            }
          });
    }
  }

  /**
   * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of
   * its model i.e. <tt>callPeer</tt>.
   */
  private void updateViewFromModel() {
    /*
     * We receive events/notifications from various threads and we respond
     * to them in the AWT event dispatching thread. It is possible to first
     * schedule an event to be brought to the AWT event dispatching thread,
     * then to have #dispose() invoked on this instance and, finally, to
     * receive the scheduled event in the AWT event dispatching thread. In
     * such a case, this disposed instance should not respond to the event
     * because it may, for example, steal a visual Components depicting
     * video (which cannot belong to more than one parent at a time) from
     * another non-disposed OneToOneCallPeerPanel.
     */
    if (!disposed) {
      if (SwingUtilities.isEventDispatchThread()) updateViewFromModelInEventDispatchThread();
      else {
        SwingUtilities.invokeLater(updateViewFromModelInEventDispatchThread);
      }
    }
  }

  /**
   * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of
   * its model i.e. <tt>callPeer</tt>. The update is performed in the AWT event dispatching thread.
   */
  private void updateViewFromModelInEventDispatchThread() {
    /*
     * We receive events/notifications from various threads and we respond
     * to them in the AWT event dispatching thread. It is possible to first
     * schedule an event to be brought to the AWT event dispatching thread,
     * then to have #dispose() invoked on this instance and, finally, to
     * receive the scheduled event in the AWT event dispatching thread. In
     * such a case, this disposed instance should not respond to the event
     * because it may, for example, steal a visual Components depicting
     * video (which cannot belong to more than one parent at a time) from
     * another non-disposed OneToOneCallPeerPanel.
     */
    if (disposed) return;

    /*
     * Update the display of visual <tt>Component</tt>s depicting video
     * streaming between the local peer/user and the remote peer(s).
     */

    OperationSetVideoTelephony videoTelephony =
        callPeer.getProtocolProvider().getOperationSet(OperationSetVideoTelephony.class);
    Component remoteVideo = null;
    Component localVideo = null;

    if (videoTelephony != null) {
      List<Component> remoteVideos = videoTelephony.getVisualComponents(callPeer);

      if ((remoteVideos != null) && !remoteVideos.isEmpty()) {
        /*
         * TODO OneToOneCallPeerPanel displays a one-to-one conversation
         * between the local peer/user and a specific remote peer. If
         * the remote peer is the focus of a telephony conference of its
         * own, it may be sending multiple videos to the local peer.
         * Switching to a user interface which displays multiple videos
         * is the responsibility of whoever decided that this
         * OneToOneCallPeerPanel is to be used to depict the current
         * state of the CallConference associated with the CallPeer
         * depicted by this instance. If that switching decides that
         * this instance is to continue being the user interface, then
         * we should probably pick up the remote video which is
         * generated by the remote peer and not one of its
         * ConferenceMembers.
         */
        remoteVideo = remoteVideos.get(0);
      }

      if (uiVideoHandler.isLocalVideoVisible()) {
        try {
          localVideo = videoTelephony.getLocalVisualComponent(callPeer);
        } catch (OperationFailedException ofe) {
          /*
           * Well, we cannot do much about the exception. We'll just
           * not display the local video.
           */
          logger.warn("Failed to retrieve local video to be displayed.", ofe);
        }
      }

      /*
       * Determine whether there is actually a change in the local and
       * remote videos which requires an update.
       */
      boolean localVideoChanged =
          ((localVideo != this.localVideo)
              || ((localVideo != null) && !UIVideoHandler2.isAncestor(center, localVideo)));
      boolean remoteVideoChanged =
          ((remoteVideo != this.remoteVideo)
              || ((remoteVideo != null) && !UIVideoHandler2.isAncestor(center, remoteVideo)));

      // If the remote video has changed, maybe the CallPanel can display
      // the LO/SD/HD button.
      if (remoteVideoChanged) {
        // Updates video component which may listen the mouse and key
        // events.
        if (desktopSharingMouseAndKeyboardListener != null) {
          desktopSharingMouseAndKeyboardListener.setVideoComponent(remoteVideo);
        }

        CallPanel callPanel = callRenderer.getCallContainer();
        // The remote video has been added, then tries to display the
        // LO/SD/HD button.
        if (remoteVideo != null) {
          callPanel.addRemoteVideoSpecificComponents(callPeer);
        }
        // The remote video has been removed, then hide the LO/SD/HD
        // button if it is currently displayed.
        else {
          callPanel.removeRemoteVideoSpecificComponents();
        }
      }

      if (localVideoChanged || remoteVideoChanged) {
        /*
         * VideoContainer and JAWTRenderer cannot handle random
         * additions of Components. Removing the localVideo when the
         * user has requests its hiding though, should work without
         * removing all Components from the VideoCotainer and adding
         * them again.
         */
        if (localVideoChanged && !remoteVideoChanged && (localVideo == null)) {
          if (this.localVideo != null) {
            center.remove(this.localVideo);
            this.localVideo = null;

            if (closeLocalVisualComponentButton != null)
              center.remove(closeLocalVisualComponentButton);
          }
        } else {
          center.removeAll();
          this.localVideo = null;
          this.remoteVideo = null;

          /*
           * AWT does not make a guarantee about the Z order even
           * within an operating system i.e. the order of adding the
           * Components to their Container does not mean that they
           * will be determinedly painted in that or reverse order.
           * Anyway, there appears to be an expectation among the
           * developers less acquainted with AWT that AWT paints the
           * Components of a Container in an order that is the reverse
           * of the order of their adding. In order to satisfy that
           * expectation and thus give at least some idea to the
           * developers reading the code bellow, do add the Components
           * according to that expectation.
           */

          if (localVideo != null) {
            if (closeLocalVisualComponentButton == null) {
              closeLocalVisualComponentButton = new CloseLocalVisualComponentButton(uiVideoHandler);
            }
            center.add(closeLocalVisualComponentButton, VideoLayout.CLOSE_LOCAL_BUTTON, -1);

            center.add(localVideo, VideoLayout.LOCAL, -1);
            this.localVideo = localVideo;
          }

          if (remoteVideo != null) {
            center.add(remoteVideo, VideoLayout.CENTER_REMOTE, -1);
            this.remoteVideo = remoteVideo;
          }
        }
      }
    }
  }

  /** The <tt>TransparentPanel</tt> that will display the peer status. */
  private static class PeerStatusPanel extends TransparentPanel {
    /**
     * Silence the serial warning. Though there isn't a plan to serialize the instances of the
     * class, there're no fields so the default serialization routine will work.
     */
    private static final long serialVersionUID = 0L;

    /**
     * Constructs a new <tt>PeerStatusPanel</tt>.
     *
     * @param layout the <tt>LayoutManager</tt> to use
     */
    public PeerStatusPanel(LayoutManager layout) {
      super(layout);
    }

    /** @{inheritDoc} */
    @Override
    public void paintComponent(Graphics g) {
      super.paintComponent(g);

      g = g.create();

      try {
        AntialiasingManager.activateAntialiasing(g);

        g.setColor(Color.DARK_GRAY);
        g.fillRoundRect(0, 0, this.getWidth(), this.getHeight(), 10, 10);
      } finally {
        g.dispose();
      }
    }
  }
}
Beispiel #17
0
public class ShowPreviewDialog extends SIPCommDialog
    implements ActionListener, ChatLinkClickedListener {
  /** Serial version UID. */
  private static final long serialVersionUID = 1L;

  /**
   * The <tt>Logger</tt> used by the <tt>ShowPreviewDialog</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(ShowPreviewDialog.class);

  ConfigurationService cfg = GuiActivator.getConfigurationService();

  /** The Ok button. */
  private final JButton okButton;

  /** The cancel button. */
  private final JButton cancelButton;

  /** Checkbox that indicates whether or not to show this dialog next time. */
  private final JCheckBox enableReplacementProposal;

  /** Checkbox that indicates whether or not to show previews automatically */
  private final JCheckBox enableReplacement;

  /** The <tt>ChatConversationPanel</tt> that this dialog is associated with. */
  private final ChatConversationPanel chatPanel;

  /** Mapping between messageID and the string representation of the chat message. */
  private Map<String, String> msgIDToChatString = new ConcurrentHashMap<String, String>();

  /**
   * Mapping between the pair (messageID, link position) and the actual link in the string
   * representation of the chat message.
   */
  private Map<String, String> msgIDandPositionToLink = new ConcurrentHashMap<String, String>();

  /**
   * Mapping between link and replacement for this link that is acquired from it's corresponding
   * <tt>ReplacementService</tt>.
   */
  private Map<String, String> linkToReplacement = new ConcurrentHashMap<String, String>();

  /** The id of the message that is currently associated with this dialog. */
  private String currentMessageID = "";

  /** The position of the link in the current message. */
  private String currentLinkPosition = "";

  /**
   * Creates an instance of <tt>ShowPreviewDialog</tt>
   *
   * @param chatPanel The <tt>ChatConversationPanel</tt> that is associated with this dialog.
   */
  ShowPreviewDialog(final ChatConversationPanel chatPanel) {
    this.chatPanel = chatPanel;

    this.setTitle(
        GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_DIALOG_TITLE"));
    okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK"));
    cancelButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL"));

    JPanel mainPanel = new TransparentPanel();
    mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
    mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    // mainPanel.setPreferredSize(new Dimension(200, 150));
    this.getContentPane().add(mainPanel);

    JTextPane descriptionMsg = new JTextPane();
    descriptionMsg.setEditable(false);
    descriptionMsg.setOpaque(false);
    descriptionMsg.setText(
        GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_WARNING_DESCRIPTION"));

    Icon warningIcon = null;
    try {
      warningIcon =
          new ImageIcon(
              ImageIO.read(
                  GuiActivator.getResources().getImageURL("service.gui.icons.WARNING_ICON")));
    } catch (IOException e) {
      logger.debug("failed to load the warning icon");
    }
    JLabel warningSign = new JLabel(warningIcon);

    JPanel warningPanel = new TransparentPanel();
    warningPanel.setLayout(new BoxLayout(warningPanel, BoxLayout.X_AXIS));
    warningPanel.add(warningSign);
    warningPanel.add(Box.createHorizontalStrut(10));
    warningPanel.add(descriptionMsg);

    enableReplacement =
        new JCheckBox(
            GuiActivator.getResources()
                .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS"));
    enableReplacement.setOpaque(false);
    enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true));
    enableReplacementProposal =
        new JCheckBox(
            GuiActivator.getResources()
                .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL"));
    enableReplacementProposal.setOpaque(false);

    JPanel checkBoxPanel = new TransparentPanel();
    checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.Y_AXIS));
    checkBoxPanel.add(enableReplacement);
    checkBoxPanel.add(enableReplacementProposal);

    JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));
    buttonsPanel.add(okButton);
    buttonsPanel.add(cancelButton);

    mainPanel.add(warningPanel);
    mainPanel.add(Box.createVerticalStrut(10));
    mainPanel.add(checkBoxPanel);
    mainPanel.add(buttonsPanel);

    okButton.addActionListener(this);
    cancelButton.addActionListener(this);

    this.setPreferredSize(new Dimension(390, 230));
  }

  @Override
  public void actionPerformed(ActionEvent arg0) {
    if (arg0.getSource().equals(okButton)) {
      cfg.setProperty(ReplacementProperty.REPLACEMENT_ENABLE, enableReplacement.isSelected());
      cfg.setProperty(
          ReplacementProperty.REPLACEMENT_PROPOSAL, enableReplacementProposal.isSelected());
      SwingWorker worker =
          new SwingWorker() {
            /**
             * Called on the event dispatching thread (not on the worker thread) after the <code>
             * construct</code> method has returned.
             */
            @Override
            public void finished() {
              String newChatString = (String) get();

              if (newChatString != null) {
                try {
                  Element elem = chatPanel.document.getElement(currentMessageID);
                  chatPanel.document.setOuterHTML(elem, newChatString);
                  msgIDToChatString.put(currentMessageID, newChatString);
                } catch (BadLocationException ex) {
                  logger.error("Could not replace chat message", ex);
                } catch (IOException ex) {
                  logger.error("Could not replace chat message", ex);
                }
              }
            }

            @Override
            protected Object construct() throws Exception {
              String newChatString = msgIDToChatString.get(currentMessageID);
              try {
                String originalLink =
                    msgIDandPositionToLink.get(currentMessageID + "#" + currentLinkPosition);
                String replacementLink = linkToReplacement.get(originalLink);
                String replacement;
                DirectImageReplacementService source =
                    GuiActivator.getDirectImageReplacementSource();
                if (originalLink.equals(replacementLink)
                    && (!source.isDirectImage(originalLink)
                        || source.getImageSize(originalLink) == -1)) {
                  replacement = originalLink;
                } else {
                  replacement =
                      "<IMG HEIGHT=\"90\" WIDTH=\"120\" SRC=\""
                          + replacementLink
                          + "\" BORDER=\"0\" ALT=\""
                          + originalLink
                          + "\"></IMG>";
                }

                String old =
                    originalLink
                        + "</A> <A href=\"jitsi://"
                        + ShowPreviewDialog.this.getClass().getName()
                        + "/SHOWPREVIEW?"
                        + currentMessageID
                        + "#"
                        + currentLinkPosition
                        + "\">"
                        + GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW");

                newChatString = newChatString.replace(old, replacement);
              } catch (Exception ex) {
                logger.error("Could not replace chat message", ex);
              }
              return newChatString;
            }
          };
      worker.start();
      this.setVisible(false);
    } else if (arg0.getSource().equals(cancelButton)) {
      this.setVisible(false);
    }
  }

  @Override
  public void chatLinkClicked(URI url) {
    String action = url.getPath();
    if (action.equals("/SHOWPREVIEW")) {
      enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true));
      enableReplacementProposal.setSelected(
          cfg.getBoolean(ReplacementProperty.REPLACEMENT_PROPOSAL, true));

      currentMessageID = url.getQuery();
      currentLinkPosition = url.getFragment();

      this.setVisible(true);
      this.setLocationRelativeTo(chatPanel);
    }
  }

  /**
   * Returns mapping between messageID and the string representation of the chat message.
   *
   * @return mapping between messageID and chat string.
   */
  Map<String, String> getMsgIDToChatString() {
    return msgIDToChatString;
  }

  /**
   * Returns mapping between the pair (messageID, link position) and the actual link in the string
   * representation of the chat message.
   *
   * @return mapping between (messageID, linkPosition) and link.
   */
  Map<String, String> getMsgIDandPositionToLink() {
    return msgIDandPositionToLink;
  }

  /**
   * Returns mapping between link and replacement for this link that was acquired from it's
   * corresponding <tt>ReplacementService</tt>.
   *
   * @return mapping between link and it's corresponding replacement.
   */
  Map<String, String> getLinkToReplacement() {
    return linkToReplacement;
  }
}
/**
 * The advanced configuration panel.
 *
 * @author Yana Stamcheva
 */
public class AdvancedConfigurationPanel extends TransparentPanel
    implements ConfigurationForm, ConfigurationContainer, ServiceListener, ListSelectionListener {
  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  /** The <tt>Logger</tt> used by this <tt>AdvancedConfigurationPanel</tt> for logging output. */
  private final Logger logger = Logger.getLogger(AdvancedConfigurationPanel.class);

  /** The configuration list. */
  private final JList configList = new JList();

  /** The center panel. */
  private final JPanel centerPanel = new TransparentPanel(new BorderLayout());

  /** Creates an instance of the <tt>AdvancedConfigurationPanel</tt>. */
  public AdvancedConfigurationPanel() {
    super(new BorderLayout(10, 0));

    initList();

    centerPanel.setPreferredSize(new Dimension(500, 500));

    add(centerPanel, BorderLayout.CENTER);
  }

  /** Initializes the config list. */
  private void initList() {
    configList.setModel(new DefaultListModel());
    configList.setCellRenderer(new ConfigListCellRenderer());
    configList.addListSelectionListener(this);
    configList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

    JScrollPane configScrollList = new JScrollPane();

    configScrollList.getVerticalScrollBar().setUnitIncrement(30);

    configScrollList.getViewport().add(configList);

    add(configScrollList, BorderLayout.WEST);

    String osgiFilter =
        "(" + ConfigurationForm.FORM_TYPE + "=" + ConfigurationForm.ADVANCED_TYPE + ")";
    ServiceReference[] confFormsRefs = null;
    try {
      confFormsRefs =
          AdvancedConfigActivator.bundleContext.getServiceReferences(
              ConfigurationForm.class.getName(), osgiFilter);
    } catch (InvalidSyntaxException ex) {
    }

    if (confFormsRefs != null) {
      for (int i = 0; i < confFormsRefs.length; i++) {
        ConfigurationForm form =
            (ConfigurationForm) AdvancedConfigActivator.bundleContext.getService(confFormsRefs[i]);

        if (form.isAdvanced()) this.addConfigForm(form);
      }
    }
  }

  /**
   * Shows on the right the configuration form given by the given <tt>ConfigFormDescriptor</tt>.
   *
   * @param configForm the configuration form to show
   */
  private void showFormContent(ConfigurationForm configForm) {
    this.centerPanel.removeAll();

    JComponent configFormPanel = (JComponent) configForm.getForm();

    configFormPanel.setOpaque(false);

    this.centerPanel.add(configFormPanel, BorderLayout.CENTER);

    this.centerPanel.revalidate();
    this.centerPanel.repaint();
  }

  /**
   * Handles registration of a new configuration form.
   *
   * @param event the <tt>ServiceEvent</tt> that notified us
   */
  public void serviceChanged(ServiceEvent event) {
    Object sService = AdvancedConfigActivator.bundleContext.getService(event.getServiceReference());

    // we don't care if the source service is not a configuration form
    if (!(sService instanceof ConfigurationForm)) return;

    ConfigurationForm configForm = (ConfigurationForm) sService;

    /*
     * This AdvancedConfigurationPanel is an advanced ConfigurationForm so
     * don't try to add it to itself.
     */
    if ((configForm == this) || !configForm.isAdvanced()) return;

    switch (event.getType()) {
      case ServiceEvent.REGISTERED:
        if (logger.isInfoEnabled())
          logger.info("Handling registration of a new Configuration Form.");

        this.addConfigForm(configForm);
        break;

      case ServiceEvent.UNREGISTERING:
        this.removeConfigForm(configForm);
        break;
    }
  }

  /**
   * Adds a new <tt>ConfigurationForm</tt> to this list.
   *
   * @param configForm The <tt>ConfigurationForm</tt> to add.
   */
  public void addConfigForm(ConfigurationForm configForm) {
    if (configForm == null) throw new IllegalArgumentException("configForm");

    DefaultListModel listModel = (DefaultListModel) configList.getModel();

    int i = 0;
    int count = listModel.getSize();
    int configFormIndex = configForm.getIndex();
    for (; i < count; i++) {
      ConfigurationForm form = (ConfigurationForm) listModel.get(i);

      if (configFormIndex < form.getIndex()) break;
    }
    listModel.add(i, configForm);
  }

  /**
   * Implements <code>ApplicationWindow.show</code> method.
   *
   * @param isVisible specifies whether the frame is to be visible or not.
   */
  @Override
  public void setVisible(boolean isVisible) {
    if (isVisible && configList.getSelectedIndex() < 0) {
      this.configList.setSelectedIndex(0);
    }
    super.setVisible(isVisible);
  }

  /**
   * Removes a <tt>ConfigurationForm</tt> from this list.
   *
   * @param configForm The <tt>ConfigurationForm</tt> to remove.
   */
  public void removeConfigForm(ConfigurationForm configForm) {
    DefaultListModel listModel = (DefaultListModel) configList.getModel();

    for (int count = listModel.getSize(), i = count - 1; i >= 0; i--) {
      ConfigurationForm form = (ConfigurationForm) listModel.get(i);

      if (form.equals(configForm)) {
        listModel.remove(i);
        /*
         * TODO We may just consider not allowing duplicates on addition
         * and then break here.
         */
      }
    }
  }

  /** A custom cell renderer that represents a <tt>ConfigurationForm</tt>. */
  private class ConfigListCellRenderer extends DefaultListCellRenderer {
    /** Serial version UID. */
    private static final long serialVersionUID = 0L;

    private boolean isSelected = false;

    private final Color selectedColor =
        new Color(
            AdvancedConfigActivator.getResources().getColor("service.gui.LIST_SELECTION_COLOR"));

    /**
     * Creates an instance of <tt>ConfigListCellRenderer</tt> and specifies that this renderer is
     * transparent.
     */
    public ConfigListCellRenderer() {
      this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      this.setOpaque(false);
    }

    /**
     * Returns the component representing the cell given by parameters.
     *
     * @param list the parent list
     * @param value the value of the cell
     * @param index the index of the cell
     * @param isSelected indicates if the cell is selected
     * @param cellHasFocus indicates if the cell has the focus
     * @return the component representing the cell
     */
    public Component getListCellRendererComponent(
        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      ConfigurationForm configForm = (ConfigurationForm) value;

      this.isSelected = isSelected;
      this.setText(configForm.getTitle());

      return this;
    }

    /**
     * Paint a background for all groups and a round blue border and background when a cell is
     * selected.
     *
     * @param g the <tt>Graphics</tt> object
     */
    public void paintComponent(Graphics g) {
      Graphics g2 = g.create();
      try {
        internalPaintComponent(g2);
      } finally {
        g2.dispose();
      }
      super.paintComponent(g);
    }

    /**
     * Paint a background for all groups and a round blue border and background when a cell is
     * selected.
     *
     * @param g the <tt>Graphics</tt> object
     */
    private void internalPaintComponent(Graphics g) {
      AntialiasingManager.activateAntialiasing(g);

      Graphics2D g2 = (Graphics2D) g;

      if (isSelected) {
        g2.setColor(selectedColor);
        g2.fillRect(0, 0, this.getWidth(), this.getHeight());
      }
    }
  }

  /**
   * Called when user selects a component in the list of configuration forms.
   *
   * @param e the <tt>ListSelectionEvent</tt>
   */
  public void valueChanged(ListSelectionEvent e) {
    if (!e.getValueIsAdjusting()) {
      ConfigurationForm configForm = (ConfigurationForm) configList.getSelectedValue();

      if (configForm != null) showFormContent(configForm);
    }
  }

  /**
   * Selects the given <tt>ConfigurationForm</tt>.
   *
   * @param configForm the <tt>ConfigurationForm</tt> to select
   */
  public void setSelected(ConfigurationForm configForm) {
    configList.setSelectedValue(configForm, true);
  }

  /**
   * Returns the title of the form.
   *
   * @return the title of the form
   */
  public String getTitle() {
    return AdvancedConfigActivator.getResources().getI18NString("service.gui.ADVANCED");
  }

  /**
   * Returns the icon of the form.
   *
   * @return a byte array containing the icon of the form
   */
  public byte[] getIcon() {
    return AdvancedConfigActivator.getResources()
        .getImageInBytes("plugin.advancedconfig.PLUGIN_ICON");
  }

  /**
   * Returns the form component.
   *
   * @return the form component
   */
  public Object getForm() {
    return this;
  }

  /**
   * Returns the index of the form in its parent container.
   *
   * @return the index of the form in its parent container
   */
  public int getIndex() {
    return 300;
  }

  /**
   * Indicates if the form is an advanced form.
   *
   * @return <tt>true</tt> to indicate that this is an advanced form, otherwise returns
   *     <tt>false</tt>
   */
  public boolean isAdvanced() {
    return false;
  }

  /**
   * Validates the currently selected configuration form. This method is meant to be used by
   * configuration forms the re-validate when a new component has been added or size has changed.
   */
  public void validateCurrentForm() {}
}
Beispiel #19
0
/**
 * The class listens for "focus joined room" and "conference created" events and adds the info about
 * all conference components versions to Jicofo's MUC presence.
 *
 * @author Pawel Domas
 */
public class VersionBroadcaster extends EventHandlerActivator {
  /** The logger */
  private static final Logger logger = Logger.getLogger(VersionBroadcaster.class);

  /** <tt>FocusManager</tt> instance used to access <tt>JitsiMeetConference</tt>. */
  private FocusManager focusManager;

  /** <tt>VersionService</tt> which provides Jicofo version. */
  private VersionService versionService;

  /** Jitsi Meet tools used to add packet extension to Jicofo presence. */
  private OperationSetJitsiMeetTools meetTools;

  /** Creates new instance of <tt>VersionBroadcaster</tt>. */
  public VersionBroadcaster() {
    super(new String[] {EventFactory.FOCUS_JOINED_ROOM_TOPIC, EventFactory.CONFERENCE_ROOM_TOPIC});
  }

  /** {@inheritDoc} */
  @Override
  public void start(BundleContext bundleContext) throws Exception {
    focusManager = ServiceUtils.getService(bundleContext, FocusManager.class);

    Assert.notNull(focusManager, "focusManager");

    versionService = ServiceUtils.getService(bundleContext, VersionService.class);

    Assert.notNull(versionService, "versionService");

    meetTools = focusManager.getOperationSet(OperationSetJitsiMeetTools.class);

    Assert.notNull(meetTools, "meetTools");

    super.start(bundleContext);
  }

  /** {@inheritDoc} */
  @Override
  public void stop(BundleContext bundleContext) throws Exception {
    super.stop(bundleContext);

    focusManager = null;
    versionService = null;
    meetTools = null;
  }

  /**
   * Handles {@link EventFactory#FOCUS_JOINED_ROOM_TOPIC} and {@link
   * EventFactory#CONFERENCE_ROOM_TOPIC}.
   *
   * <p>{@inheritDoc}
   */
  @Override
  public void handleEvent(Event event) {
    String topic = event.getTopic();
    if (!topic.equals(EventFactory.FOCUS_JOINED_ROOM_TOPIC)
        && !topic.equals(EventFactory.CONFERENCE_ROOM_TOPIC)) {
      logger.error("Unexpected event topic: " + topic);
      return;
    }

    String roomJid = (String) event.getProperty(EventFactory.ROOM_JID_KEY);

    JitsiMeetConference conference = focusManager.getConference(roomJid);
    if (conference == null) {
      logger.error("Conference is null");
      return;
    }

    ChatRoom chatRoom = conference.getChatRoom();
    if (chatRoom == null) {
      logger.error("Chat room is null");
      return;
    }

    JitsiMeetServices meetServices = focusManager.getJitsiMeetServices();
    ComponentVersionsExtension versionsExtension = new ComponentVersionsExtension();

    // XMPP
    Version xmppServerVersion = meetServices.getXMPPServerVersion();
    if (xmppServerVersion != null) {
      versionsExtension.addComponentVersion(
          ComponentVersionsExtension.COMPONENT_XMPP_SERVER,
          xmppServerVersion.getNameVersionOsString());
    }

    // Conference focus
    org.jitsi.service.version.Version jicofoVersion = versionService.getCurrentVersion();
    versionsExtension.addComponentVersion(
        ComponentVersionsExtension.COMPONENT_FOCUS,
        jicofoVersion.getApplicationName()
            + "("
            + jicofoVersion.toString()
            + ","
            + System.getProperty("os.name")
            + ")");

    // Videobridge
    // It is not be reported for FOCUS_JOINED_ROOM_TOPIC
    String bridgeJid = (String) event.getProperty(EventFactory.BRIDGE_JID_KEY);
    Version jvbVersion = bridgeJid == null ? null : meetServices.getBridgeVersion(bridgeJid);
    if (jvbVersion != null) {
      versionsExtension.addComponentVersion(
          ComponentVersionsExtension.COMPONENT_VIDEOBRIDGE, jvbVersion.getNameVersionOsString());
    }

    meetTools.sendPresenceExtension(chatRoom, versionsExtension);

    if (logger.isDebugEnabled()) logger.debug("Sending versions: " + versionsExtension.toXML());
  }
}
/**
 * The <tt>GibberishAccountRegistrationWizard</tt> is an implementation of the
 * <tt>AccountRegistrationWizard</tt> for the Gibberish protocol. It allows the user to create and
 * configure a new Gibberish account.
 *
 * @author Emil Ivov
 */
public class GibberishAccountRegistrationWizard extends DesktopAccountRegistrationWizard {
  private final Logger logger = Logger.getLogger(GibberishAccountRegistrationWizard.class);

  /** The first page of the gibberish account registration wizard. */
  private FirstWizardPage firstWizardPage;

  /** The object that we use to store details on an account that we will be creating. */
  private GibberishAccountRegistration registration = new GibberishAccountRegistration();

  private ProtocolProviderService protocolProvider;

  /**
   * Creates an instance of <tt>GibberishAccountRegistrationWizard</tt>.
   *
   * @param wizardContainer the wizard container, where this wizard is added
   */
  public GibberishAccountRegistrationWizard(WizardContainer wizardContainer) {
    setWizardContainer(wizardContainer);

    wizardContainer.setFinishButtonText(Resources.getString("service.gui.SIGN_IN"));
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getIcon</code> method. Returns the icon to be
   * used for this wizard.
   *
   * @return byte[]
   */
  public byte[] getIcon() {
    return Resources.getImage(Resources.GIBBERISH_LOGO);
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getPageImage</code> method. Returns the image
   * used to decorate the wizard page
   *
   * @return byte[] the image used to decorate the wizard page
   */
  public byte[] getPageImage() {
    return Resources.getImage(Resources.PAGE_IMAGE);
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getProtocolName</code> method. Returns the
   * protocol name for this wizard.
   *
   * @return String
   */
  public String getProtocolName() {
    return Resources.getString("plugin.gibberishaccregwizz.PROTOCOL_NAME");
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getProtocolDescription
   * </code> method. Returns the description of the protocol for this wizard.
   *
   * @return String
   */
  public String getProtocolDescription() {
    return Resources.getString("plugin.gibberishaccregwizz.PROTOCOL_DESCRIPTION");
  }

  /**
   * Returns the set of pages contained in this wizard.
   *
   * @return Iterator
   */
  public Iterator<WizardPage> getPages() {
    java.util.List<WizardPage> pages = new ArrayList<WizardPage>();
    firstWizardPage = new FirstWizardPage(this);

    pages.add(firstWizardPage);

    return pages.iterator();
  }

  /**
   * Returns the set of data that user has entered through this wizard.
   *
   * @return Iterator
   */
  public Iterator<Map.Entry<String, String>> getSummary() {
    Map<String, String> summaryTable = new Hashtable<String, String>();

    summaryTable.put("User ID", registration.getUserID());

    return summaryTable.entrySet().iterator();
  }

  /**
   * Defines the operations that will be executed when the user clicks on the wizard "Signin"
   * button.
   *
   * @return the created <tt>ProtocolProviderService</tt> corresponding to the new account
   * @throws OperationFailedException if the operation didn't succeed
   */
  public ProtocolProviderService signin() throws OperationFailedException {
    firstWizardPage.commitPage();

    return signin(registration.getUserID(), null);
  }

  /**
   * Defines the operations that will be executed when the user clicks on the wizard "Signin"
   * button.
   *
   * @param userName the user name to sign in with
   * @param password the password to sign in with
   * @return the created <tt>ProtocolProviderService</tt> corresponding to the new account
   * @throws OperationFailedException if the operation didn't succeed
   */
  public ProtocolProviderService signin(String userName, String password)
      throws OperationFailedException {
    ProtocolProviderFactory factory =
        GibberishAccRegWizzActivator.getGibberishProtocolProviderFactory();

    return this.installAccount(factory, userName);
  }

  /**
   * Creates an account for the given user and password.
   *
   * @param providerFactory the ProtocolProviderFactory which will create the account
   * @param user the user identifier
   * @return the <tt>ProtocolProviderService</tt> for the new account.
   */
  public ProtocolProviderService installAccount(
      ProtocolProviderFactory providerFactory, String user) throws OperationFailedException {
    Hashtable<String, String> accountProperties = new Hashtable<String, String>();

    accountProperties.put(
        ProtocolProviderFactory.ACCOUNT_ICON_PATH,
        "resources/images/protocol/gibberish/gibberish32x32.png");

    if (registration.isRememberPassword()) {
      accountProperties.put(ProtocolProviderFactory.PASSWORD, registration.getPassword());
    }

    if (isModification()) {
      providerFactory.uninstallAccount(protocolProvider.getAccountID());
      this.protocolProvider = null;
      setModification(false);
    }

    try {
      AccountID accountID = providerFactory.installAccount(user, accountProperties);

      ServiceReference serRef = providerFactory.getProviderForAccount(accountID);

      protocolProvider =
          (ProtocolProviderService) GibberishAccRegWizzActivator.bundleContext.getService(serRef);
    } catch (IllegalStateException exc) {
      logger.warn(exc.getMessage());

      throw new OperationFailedException(
          "Account already exists.", OperationFailedException.IDENTIFICATION_CONFLICT);
    } catch (Exception exc) {
      logger.warn(exc.getMessage());

      throw new OperationFailedException(
          "Failed to add account", OperationFailedException.GENERAL_ERROR);
    }

    return protocolProvider;
  }

  /**
   * Fills the UserID and Password fields in this panel with the data comming from the given
   * protocolProvider.
   *
   * @param protocolProvider The <tt>ProtocolProviderService</tt> to load the data from.
   */
  public void loadAccount(ProtocolProviderService protocolProvider) {
    setModification(true);

    this.protocolProvider = protocolProvider;

    this.registration = new GibberishAccountRegistration();

    this.firstWizardPage.loadAccount(protocolProvider);
  }

  /**
   * Returns the registration object, which will store all the data through the wizard.
   *
   * @return the registration object, which will store all the data through the wizard
   */
  public GibberishAccountRegistration getRegistration() {
    return registration;
  }

  /**
   * Returns the size of this wizard.
   *
   * @return the size of this wizard
   */
  public Dimension getSize() {
    return new Dimension(600, 500);
  }

  /**
   * Returns the identifier of the page to show first in the wizard.
   *
   * @return the identifier of the page to show first in the wizard.
   */
  public Object getFirstPageIdentifier() {
    return firstWizardPage.getIdentifier();
  }

  /**
   * Returns the identifier of the page to show last in the wizard.
   *
   * @return the identifier of the page to show last in the wizard.
   */
  public Object getLastPageIdentifier() {
    return firstWizardPage.getIdentifier();
  }

  /**
   * Returns an example string, which should indicate to the user how the user name should look
   * like.
   *
   * @return an example string, which should indicate to the user how the user name should look
   *     like.
   */
  public String getUserNameExample() {
    return FirstWizardPage.USER_NAME_EXAMPLE;
  }

  /**
   * Indicates whether this wizard enables the simple "sign in" form shown when the user opens the
   * application for the first time. The simple "sign in" form allows user to configure her account
   * in one click, just specifying her username and password and leaving any other configuration as
   * by default.
   *
   * @return <code>true</code> if the simple "Sign in" form is enabled or <code>false</code>
   *     otherwise.
   */
  public boolean isSimpleFormEnabled() {
    return false;
  }

  /**
   * Returns a simple account registration form that would be the first form shown to the user. Only
   * if the user needs more settings she'll choose to open the advanced wizard, consisted by all
   * pages.
   *
   * @param isCreateAccount indicates if the simple form should be opened as a create account form
   *     or as a login form
   * @return a simple account registration form
   */
  public Object getSimpleForm(boolean isCreateAccount) {
    firstWizardPage = new FirstWizardPage(this);
    return firstWizardPage.getSimpleForm();
  }
}
Beispiel #21
0
/**
 * @author Yana Stamcheva
 * @author Pawel Domas
 */
public class AndroidCallUtil {
  /** The logger for this class. */
  private static final Logger logger = Logger.getLogger(AndroidCallUtil.class);

  /** Field used to track the thread used to create outgoing calls. */
  private static Thread createCallThread;

  /**
   * Creates an android call.
   *
   * @param context the android context
   * @param callButtonView the button view that generated the call
   * @param contact the contact address to call
   */
  public static void createAndroidCall(Context context, View callButtonView, String contact) {
    if (AccountUtils.getRegisteredProviders().size() > 1)
      showCallViaMenu(context, callButtonView, contact);
    else createCall(context, contact);
  }

  /**
   * Creates new call to target <tt>destination</tt>.
   *
   * @param context the android context
   * @param destination the target callee name that will be used.
   */
  private static void createCall(Context context, String destination) {
    Iterator<ProtocolProviderService> allProviders =
        AccountUtils.getRegisteredProviders().iterator();

    if (!allProviders.hasNext()) {
      logger.error("No registered providers found");
      return;
    }

    createCall(context, destination, allProviders.next());
  }

  /**
   * Creates new call to given <tt>destination</tt> using selected <tt>provider</tt>.
   *
   * @param context the android context
   * @param destination target callee name.
   * @param provider the provider that will be used to make a call.
   */
  public static void createCall(
      final Context context, final String destination, final ProtocolProviderService provider) {
    if (createCallThread != null) {
      logger.warn("Another call is already being created");
      return;
    } else if (CallManager.getActiveCallsCount() > 0) {
      logger.warn("Another call is in progress");
      return;
    }

    final long dialogId =
        ProgressDialogFragment.showProgressDialog(
            JitsiApplication.getResString(R.string.service_gui_OUTGOING_CALL),
            JitsiApplication.getResString(R.string.service_gui_OUTGOING_CALL_MSG, destination));

    createCallThread =
        new Thread("Create call thread") {
          public void run() {
            try {
              CallManager.createCall(provider, destination);
            } catch (Throwable t) {
              logger.error("Error creating the call: " + t.getMessage(), t);
              AndroidUtils.showAlertDialog(
                  context, context.getString(R.string.service_gui_ERROR), t.getMessage());
            } finally {
              if (DialogActivity.waitForDialogOpened(dialogId)) {
                DialogActivity.closeDialog(JitsiApplication.getGlobalContext(), dialogId);
              } else {
                logger.error("Failed to wait for the dialog: " + dialogId);
              }
              createCallThread = null;
            }
          }
        };

    createCallThread.start();
  }

  /**
   * Shows "call via" menu allowing user to selected from multiple providers.
   *
   * @param context the android context
   * @param v the View that will contain the popup menu.
   * @param destination target callee name.
   */
  private static void showCallViaMenu(final Context context, View v, final String destination) {
    PopupMenu popup = new PopupMenu(context, v);

    Menu menu = popup.getMenu();

    Iterator<ProtocolProviderService> registeredProviders =
        AccountUtils.getRegisteredProviders().iterator();

    while (registeredProviders.hasNext()) {
      final ProtocolProviderService provider = registeredProviders.next();
      String accountAddress = provider.getAccountID().getAccountAddress();

      MenuItem menuItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, accountAddress);

      menuItem.setOnMenuItemClickListener(
          new MenuItem.OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
              createCall(context, destination, provider);

              return false;
            }
          });
    }

    popup.show();
  }

  /**
   * Checks if there is a call in progress. If true then shows a warning toast and finishes the
   * activity.
   *
   * @param activity activity doing a check.
   * @return <tt>true</tt> if there is call in progress and <tt>Activity</tt> was finished.
   */
  public static boolean checkCallInProgress(Activity activity) {
    if (CallManager.getActiveCallsCount() > 0) {
      logger.warn("Call is in progress");

      Toast t =
          Toast.makeText(activity, R.string.service_gui_WARN_CALL_IN_PROGRESS, Toast.LENGTH_SHORT);
      t.show();

      activity.finish();
      return true;
    } else {
      return false;
    }
  }
}
/**
 * A SSH implementation of the ProtocolProviderService.
 *
 * @author Shobhit Jindal
 */
public class ProtocolProviderServiceSSHImpl implements ProtocolProviderService {
  private static final Logger logger = Logger.getLogger(ProtocolProviderServiceSSHImpl.class);

  /** The name of this protocol. */
  public static final String SSH_PROTOCOL_NAME = ProtocolNames.SSH;

  //    /**
  //     * The identifier for SSH Stack
  //     * Java Secure Channel JSch
  //     */
  //    JSch jsch = new JSch();

  /** The test command given after each command to determine the reply length of the command */
  private final String testCommand = Resources.getString("testCommand");

  /** A reference to the protocol provider of UIService */
  private static ServiceReference ppUIServiceRef;

  /** Connection timeout to a remote server in milliseconds */
  private static int connectionTimeout = 30000;

  /** A reference to UI Service */
  private static UIService uiService;

  /** The id of the account that this protocol provider represents. */
  private AccountID accountID = null;

  /** We use this to lock access to initialization. */
  private Object initializationLock = new Object();

  /** The hashtable with the operation sets that we support locally. */
  private final Hashtable supportedOperationSets = new Hashtable();

  private OperationSetBasicInstantMessagingSSHImpl basicInstantMessaging;

  private OperationSetFileTransferSSHImpl fileTranfer;

  /** A list of listeners interested in changes in our registration state. */
  private Vector registrationStateListeners = new Vector();

  /** Indicates whether or not the provider is initialized and ready for use. */
  private boolean isInitialized = false;

  /** The logo corresponding to the ssh protocol. */
  private ProtocolIconSSHImpl sshIcon = new ProtocolIconSSHImpl();

  /**
   * The registration state of SSH Provider is taken to be registered by default as it doesn't
   * correspond to the state on remote server
   */
  private RegistrationState currentRegistrationState = RegistrationState.REGISTERED;

  /** The default constructor for the SSH protocol provider. */
  public ProtocolProviderServiceSSHImpl() {
    logger.trace("Creating a ssh provider.");

    try {
      // converting to milliseconds
      connectionTimeout = Integer.parseInt(Resources.getString("connectionTimeout")) * 1000;
    } catch (NumberFormatException ex) {
      logger.error("Connection Timeout set to 30 seconds");
    }
  }

  /**
   * Initializes the service implementation, and puts it in a sate where it could interoperate with
   * other services. It is strongly recomended that properties in this Map be mapped to property
   * names as specified by <tt>AccountProperties</tt>.
   *
   * @param userID the user id of the ssh account we're currently initializing
   * @param accountID the identifier of the account that this protocol provider represents.
   * @see net.java.sip.communicator.service.protocol.AccountID
   */
  protected void initialize(String userID, AccountID accountID) {
    synchronized (initializationLock) {
      this.accountID = accountID;

      // initialize the presence operationset
      OperationSetPersistentPresenceSSHImpl persistentPresence =
          new OperationSetPersistentPresenceSSHImpl(this);

      supportedOperationSets.put(
          OperationSetPersistentPresence.class.getName(), persistentPresence);

      // register it once again for those that simply need presence and
      // won't be smart enough to check for a persistent presence
      // alternative
      supportedOperationSets.put(OperationSetPresence.class.getName(), persistentPresence);

      // initialize the IM operation set
      basicInstantMessaging = new OperationSetBasicInstantMessagingSSHImpl(this);

      supportedOperationSets.put(
          OperationSetBasicInstantMessaging.class.getName(), basicInstantMessaging);

      // initialze the file transfer operation set
      fileTranfer = new OperationSetFileTransferSSHImpl(this);

      supportedOperationSets.put(OperationSetFileTransfer.class.getName(), fileTranfer);

      isInitialized = true;
    }
  }

  /**
   * Determines whether a vaild session exists for the contact of remote machine.
   *
   * @param sshContact ID of SSH Contact
   * @return <tt>true</tt> if the session is connected <tt>false</tt> otherwise
   */
  public boolean isSessionValid(ContactSSH sshContact) {
    Session sshSession = sshContact.getSSHSession();
    if (sshSession != null) if (sshSession.isConnected()) return true;

    // remove reference to an unconnected SSH Session, if any
    sshContact.setSSHSession(null);
    return false;
  }

  /**
   * Determines whether the contact is connected to shell of remote machine as a precheck for any
   * further operation
   *
   * @param sshContact ID of SSH Contact
   * @return <tt>true</tt> if the contact is connected <tt>false</tt> if the contact is not
   *     connected
   */
  public boolean isShellConnected(ContactSSH sshContact) {
    // a test command may also be run here

    if (isSessionValid(sshContact)) {
      return (sshContact.getShellChannel() != null);
    }

    /*
     * Above should be return(sshContact.getShellChannel() != null
     *                     && sshContact.getShellChannel().isConnected());
     *
     * but incorrect reply from stack for isConnected()
     */

    return false;
  }

  /**
   * Creates a shell channel to the remote machine a new jsch session is also created if the current
   * one is invalid
   *
   * @param sshContact the contact of the remote machine
   * @param firstMessage the first message
   */
  public void connectShell(final ContactSSH sshContact, final Message firstMessage) {
    sshContact.setConnectionInProgress(true);

    final UIService uiService = this.uiService;

    final Thread newConnection =
        new Thread(
            (new Runnable() {
              public void run() {
                OperationSetPersistentPresenceSSHImpl persistentPresence =
                    (OperationSetPersistentPresenceSSHImpl)
                        sshContact.getParentPresenceOperationSet();

                persistentPresence.changeContactPresenceStatus(
                    sshContact, SSHStatusEnum.CONNECTING);

                try {
                  if (!isSessionValid(sshContact)) createSSHSessionAndLogin(sshContact);

                  createShellChannel(sshContact);

                  // initalizing the reader and writers of ssh contact

                  persistentPresence.changeContactPresenceStatus(
                      sshContact, SSHStatusEnum.CONNECTED);

                  showWelcomeMessage(sshContact);

                  sshContact.setMessageType(sshContact.CONVERSATION_MESSAGE_RECEIVED);

                  sshContact.setConnectionInProgress(false);

                  Thread.sleep(1500);

                  sshContact.setCommandSent(true);

                  basicInstantMessaging.sendInstantMessage(sshContact, firstMessage);
                }
                // rigoruos Exception Checking in future
                catch (Exception ex) {
                  persistentPresence.changeContactPresenceStatus(
                      sshContact, SSHStatusEnum.NOT_AVAILABLE);

                  ex.printStackTrace();
                } finally {
                  sshContact.setConnectionInProgress(false);
                }
              }
            }));

    newConnection.start();
  }

  /**
   * Creates a channel for shell type in the current session channel types = shell, sftp, exec(X
   * forwarding), direct-tcpip(stream forwarding) etc
   *
   * @param sshContact ID of SSH Contact
   */
  public void createShellChannel(ContactSSH sshContact) throws IOException {
    try {
      Channel shellChannel = sshContact.getSSHSession().openChannel("shell");

      // initalizing the reader and writers of ssh contact
      sshContact.initializeShellIO(shellChannel.getInputStream(), shellChannel.getOutputStream());

      ((ChannelShell) shellChannel)
          .setPtyType(sshContact.getSSHConfigurationForm().getTerminalType());

      // initializing the shell
      shellChannel.connect(1000);

      sshContact.setShellChannel(shellChannel);

      sshContact.sendLine("export PS1=");
    } catch (JSchException ex) {
      sshContact.setSSHSession(null);
      throw new IOException("Unable to create shell channel to remote" + " server");
    }
  }

  /**
   * Closes the Shell channel are associated IO Streams
   *
   * @param sshContact ID of SSH Contact
   */
  public void closeShellChannel(ContactSSH sshContact) throws JSchException, IOException {
    sshContact.closeShellIO();
    sshContact.getShellChannel().disconnect();
    sshContact.setShellChannel(null);
  }

  /**
   * Creates a SSH Session with a remote machine and tries to login according to the details
   * specified by Contact An appropriate message is shown to the end user in case the login fails
   *
   * @param sshContact ID of SSH Contact
   * @throws JSchException if a JSch is unable to create a SSH Session with the remote machine
   * @throws InterruptedException if the thread is interrupted before session connected or is timed
   *     out
   * @throws OperationFailedException if not of above reasons :-)
   */
  public void createSSHSessionAndLogin(ContactSSH sshContact)
      throws JSchException, OperationFailedException, InterruptedException {
    logger.info("Creating a new SSH Session to " + sshContact.getHostName());

    // creating a new JSch Stack identifier for contact
    JSch jsch = new JSch();

    String knownHosts = (String) accountID.getAccountProperties().get("KNOWN_HOSTS_FILE");

    if (!knownHosts.equals("Optional")) jsch.setKnownHosts(knownHosts);

    String identitiyKey = (String) accountID.getAccountProperties().get("IDENTITY_FILE");

    String userName = sshContact.getUserName();

    // use the name of system user if the contact has not supplied SSH
    // details
    if (userName.equals("")) userName = System.getProperty("user.name");

    if (!identitiyKey.equals("Optional")) jsch.addIdentity(identitiyKey);

    // creating a new session for the contact
    Session session =
        jsch.getSession(
            userName, sshContact.getHostName(), sshContact.getSSHConfigurationForm().getPort());

    /**
     * Creating and associating User Info with the session User Info passes authentication from
     * sshContact to SSH Stack
     */
    SSHUserInfo sshUserInfo = new SSHUserInfo(sshContact);

    session.setUserInfo(sshUserInfo);

    /** initializing the session */
    session.connect(connectionTimeout);

    int count = 0;

    // wait for session to get connected
    while (!session.isConnected() && count <= 30000) {
      Thread.sleep(1000);
      count += 1000;
      logger.trace("SSH:" + sshContact.getHostName() + ": Sleep zzz .. ");
    }

    // if timeout have exceeded
    if (count > 30000) {
      sshContact.setSSHSession(null);
      JOptionPane.showMessageDialog(
          null, "SSH Connection attempt to " + sshContact.getHostName() + " timed out");

      // error codes are not defined yet
      throw new OperationFailedException(
          "SSH Connection attempt to " + sshContact.getHostName() + " timed out", 2);
    }

    sshContact.setJSch(jsch);
    sshContact.setSSHSession(session);

    logger.info("A new SSH Session to " + sshContact.getHostName() + " Created");
  }

  /**
   * Closes the SSH Session associated with the contact
   *
   * @param sshContact ID of SSH Contact
   */
  void closeSSHSession(ContactSSH sshContact) {
    sshContact.getSSHSession().disconnect();
    sshContact.setSSHSession(null);
  }

  /**
   * Presents the login welcome message to user
   *
   * @param sshContact ID of SSH Contact
   */
  public void showWelcomeMessage(ContactSSH sshContact) throws IOException {
    /*      //sending the command
            sshContact.sendLine(testCommand);

            String reply = "", line = "";

            // message is extracted until the test Command ie echoed back
            while(line.indexOf(testCommand) == -1)
            {
                reply += line + "\n";
                line = sshContact.getLine();
            }

            uiService.getPopupDialog().showMessagePopupDialog
                    (reply,"Message from " + sshContact.getDisplayName(),
                    uiService.getPopupDialog().INFORMATION_MESSAGE);

            if(line.startsWith(testCommand))
                while(!sshContact.getLine().contains(testCommand));

            //one line output of testCommand
            sshContact.getLine();
    */
    logger.debug("SSH: Welcome message shown");
  }

  /**
   * Returns a reference to UIServce for accessing UI related services
   *
   * @return uiService a reference to UIService
   */
  public static UIService getUIService() {
    return uiService;
  }

  /**
   * Registers the specified listener with this provider so that it would receive notifications on
   * changes of its state or other properties such as its local address and display name.
   *
   * @param listener the listener to register.
   */
  public void addRegistrationStateChangeListener(RegistrationStateChangeListener listener) {
    synchronized (registrationStateListeners) {
      if (!registrationStateListeners.contains(listener)) registrationStateListeners.add(listener);
    }
  }

  /**
   * Removes the specified registration listener so that it won't receive further notifications when
   * our registration state changes.
   *
   * @param listener the listener to remove.
   */
  public void removeRegistrationStateChangeListener(RegistrationStateChangeListener listener) {
    synchronized (registrationStateListeners) {
      registrationStateListeners.remove(listener);
    }
  }

  /**
   * Creates a <tt>RegistrationStateChangeEvent</tt> corresponding to the specified old and new
   * states and notifies all currently registered listeners.
   *
   * @param oldState the state that the provider had before the change occurred
   * @param newState the state that the provider is currently in.
   * @param reasonCode a value corresponding to one of the REASON_XXX fields of the
   *     RegistrationStateChangeEvent class, indicating the reason for this state transition.
   * @param reason a String further explaining the reason code or null if no such explanation is
   *     necessary.
   */
  private void fireRegistrationStateChanged(
      RegistrationState oldState, RegistrationState newState, int reasonCode, String reason) {
    RegistrationStateChangeEvent event =
        new RegistrationStateChangeEvent(this, oldState, newState, reasonCode, reason);

    logger.debug(
        "Dispatching " + event + " to " + registrationStateListeners.size() + " listeners.");

    Iterator listeners = null;
    synchronized (registrationStateListeners) {
      listeners = new ArrayList(registrationStateListeners).iterator();
    }

    while (listeners.hasNext()) {
      RegistrationStateChangeListener listener = (RegistrationStateChangeListener) listeners.next();

      listener.registrationStateChanged(event);
    }

    logger.trace("Done.");
  }

  /**
   * Returns the AccountID that uniquely identifies the account represented by this instance of the
   * ProtocolProviderService.
   *
   * @return the id of the account represented by this provider.
   */
  public AccountID getAccountID() {
    return accountID;
  }

  /**
   * Returns the operation set corresponding to the specified class or null if this operation set is
   * not supported by the provider implementation.
   *
   * @param opsetClass the <tt>Class</tt> of the operation set that we're looking for.
   * @return returns an OperationSet of the specified <tt>Class</tt> if the undelying implementation
   *     supports it or null otherwise.
   */
  public OperationSet getOperationSet(Class opsetClass) {
    return (OperationSet) getSupportedOperationSets().get(opsetClass.getName());
  }

  /**
   * Returns the short name of the protocol that the implementation of this provider is based upon
   * (like SIP, Jabber, ICQ/AIM, or others for example).
   *
   * @return a String containing the short name of the protocol this service is implementing (most
   *     often that would be a name in ProtocolNames).
   */
  public String getProtocolName() {
    return SSH_PROTOCOL_NAME;
  }

  /**
   * Returns the protocol display name. This is the name that would be used by the GUI to display
   * the protocol name.
   *
   * @return a String containing the display name of the protocol this service is implementing
   */
  public String getProtocolDisplayName() {
    return SSH_PROTOCOL_NAME;
  }

  /**
   * Returns the state of the registration of this protocol provider with the corresponding
   * registration service.
   *
   * @return ProviderRegistrationState
   */
  public RegistrationState getRegistrationState() {
    return currentRegistrationState;
  }

  /**
   * Returns an array containing all operation sets supported by the current implementation.
   *
   * @return a java.util.Map containing instance of all supported operation sets mapped against
   *     their class names (e.g. OperationSetPresence.class.getName()) .
   */
  public Map getSupportedOperationSets() {
    // Copy the map so that the caller is not able to modify it.
    return (Map) supportedOperationSets.clone();
  }

  /**
   * Indicates whether or not this provider is registered
   *
   * @return true if the provider is currently registered and false otherwise.
   */
  public boolean isRegistered() {
    return currentRegistrationState.equals(RegistrationState.REGISTERED);
  }

  /**
   * Starts the registration process.
   *
   * @param authority the security authority that will be used for resolving any security challenges
   *     that may be returned during the registration or at any moment while wer're registered.
   * @throws OperationFailedException with the corresponding code it the registration fails for some
   *     reason (e.g. a networking error or an implementation problem).
   */
  public void register(SecurityAuthority authority) throws OperationFailedException {
    RegistrationState oldState = currentRegistrationState;
    currentRegistrationState = RegistrationState.REGISTERED;

    // get a reference to UI Service via its Service Reference
    ppUIServiceRef = SSHActivator.getBundleContext().getServiceReference(UIService.class.getName());

    uiService = (UIService) SSHActivator.getBundleContext().getService(ppUIServiceRef);

    fireRegistrationStateChanged(
        oldState, currentRegistrationState, RegistrationStateChangeEvent.REASON_USER_REQUEST, null);
  }

  /**
   * Makes the service implementation close all open sockets and release any resources that it might
   * have taken and prepare for shutdown/garbage collection.
   */
  public void shutdown() {
    if (!isInitialized) {
      return;
    }
    logger.trace("Killing the SSH Protocol Provider.");

    if (isRegistered()) {
      try {
        // do the unregistration
        unregister();
      } catch (OperationFailedException ex) {
        // we're shutting down so we need to silence the exception here
        logger.error("Failed to properly unregister before shutting down. " + getAccountID(), ex);
      }
    }

    isInitialized = false;
  }

  /**
   * Ends the registration of this protocol provider with the current registration service.
   *
   * @throws OperationFailedException with the corresponding code it the registration fails for some
   *     reason (e.g. a networking error or an implementation problem).
   */
  public void unregister() throws OperationFailedException {
    RegistrationState oldState = currentRegistrationState;
    currentRegistrationState = RegistrationState.UNREGISTERED;

    fireRegistrationStateChanged(
        oldState, currentRegistrationState, RegistrationStateChangeEvent.REASON_USER_REQUEST, null);
  }

  /**
   * Returns the ssh protocol icon.
   *
   * @return the ssh protocol icon
   */
  public ProtocolIcon getProtocolIcon() {
    return sshIcon;
  }
}
Beispiel #23
0
/**
 * The class represents the recurring pattern structure of calendar item.
 *
 * @author Hristo Terezov
 */
public class RecurringPattern {
  /**
   * The <tt>Logger</tt> used by the <tt>RecurringPattern</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(RecurringPattern.class);

  /** Enum for the type of the pattern. */
  public enum PatternType {
    /** Daily recurrence. */
    Day((short) 0x0000),

    /** Weekly recurrence. */
    Week((short) 0x0001),

    /** Monthly recurrence. */
    Month((short) 0x0002),

    /** Monthly recurrence. */
    MonthNth((short) 0x0003),

    /** Monthly recurrence. */
    MonthEnd((short) 0x004),

    /** Monthly recurrence. */
    HjMonth((short) 0x000A),

    /** Monthly recurrence. */
    HjMonthNth((short) 0x000B),

    /** Monthly recurrence. */
    HjMonthEnd((short) 0x000C);

    /** The value of the type. */
    private short value = 0;

    /**
     * Constructs new <tt>PatternType</tt> instance.
     *
     * @param value the value.
     */
    PatternType(short value) {
      this.value = value;
    }

    /**
     * Returns the value of the <tt>PatternType</tt> instance.
     *
     * @return the value
     */
    public short getValue() {
      return value;
    }

    /**
     * Finds the <tt>PatternType</tt> by given value.
     *
     * @param value the value
     * @return the found <tt>PatternType</tt> instance or null if no type is found.
     */
    public static PatternType getFromShort(short value) {
      for (PatternType type : values()) {
        if (type.getValue() == value) {
          return type;
        }
      }
      return null;
    }
  };

  /** The value of recurFrequency field. */
  private short recurFrequency;

  /** The value of patternType field. */
  private PatternType patternType;

  /** The value of calendarType field. */
  private short calendarType;

  /** The value of firstDateTime field. */
  private int firstDateTime;

  /** The value of period field. */
  private int period;

  /** The value of slidingFlag field. */
  private int slidingFlag;

  /** The value of patternSpecific1 field. */
  private int patternSpecific1;

  /** The value of patternSpecific2 field. */
  private int patternSpecific2;

  /** The value of endType field. */
  private int endType;

  /** The value of occurenceCount field. */
  private int occurenceCount;

  /** The value of firstDow field. */
  private int firstDow;

  /** The value of deletedInstanceCount field. */
  private int deletedInstanceCount;

  /** The value of modifiedInstanceCount field. */
  private int modifiedInstanceCount;

  /** The value of startDate field. */
  private int startDate;

  /** The value of endDate field. */
  private int endDate;

  /** List with the start dates of deleted instances. */
  private List<Date> deletedInstances = new ArrayList<Date>();

  /** Array with the start dates of modified instances. */
  private int[] modifiedInstances;

  /** List of exception info structures included in the pattern. */
  private List<ExceptionInfo> exceptionInfo;

  /** The source calendar item of the recurrent series. */
  private CalendarItemTimerTask sourceTask;

  /** List of days of week when the calendar item occurred. */
  private List<Integer> allowedDaysOfWeek = new LinkedList<Integer>();

  /** The binary data of the pattern. */
  private ByteBuffer dataBuffer;

  /** Array with masks for days of week when the calendar item occurs. */
  public static int[] weekOfDayMask = {
    0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, 0x00000040
  };

  /**
   * Parses the binary data that describes the recurrent pattern.
   *
   * @param data the binary data.
   * @param sourceTask the calendar item.
   */
  public RecurringPattern(byte[] data, CalendarItemTimerTask sourceTask) {
    this.sourceTask = sourceTask;
    dataBuffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);

    int offset = 4;
    recurFrequency = dataBuffer.getShort(offset);
    offset += 2;

    patternType = PatternType.getFromShort(dataBuffer.getShort(offset));
    offset += 2;

    calendarType = dataBuffer.getShort(offset);
    offset += 2;

    firstDateTime = dataBuffer.getInt(offset);
    offset += 4;

    period = dataBuffer.getInt(offset);
    offset += 4;

    slidingFlag = dataBuffer.getInt(offset);
    offset += 4;

    switch (patternType) {
      case Week:
      case Month:
      case MonthEnd:
      case HjMonth:
      case HjMonthEnd:
        patternSpecific1 = dataBuffer.getInt(offset);
        patternSpecific2 = 0;
        offset += 4;
        if (patternType == PatternType.Week) {
          for (int day = firstDow; day < firstDow + 7; day++) {
            if ((patternSpecific1 & (weekOfDayMask[day % 7])) != 0) {
              allowedDaysOfWeek.add((day % 7) + 1);
            }
          }
        }
        break;
      case MonthNth:
      case HjMonthNth:
        patternSpecific1 = dataBuffer.getInt(offset);
        patternSpecific2 = dataBuffer.getInt(offset + 4);
        if (patternSpecific1 == 0x7f && patternSpecific2 != 0x5) {
          patternType = PatternType.Month;
        }
        for (int day = 0; day < 7; day++) {
          if ((patternSpecific1 & (weekOfDayMask[day])) != 0) {
            allowedDaysOfWeek.add((day) + 1);
          }
        }
        offset += 8;
        break;
      default:
        break;
    }

    // endType
    endType = dataBuffer.getInt(offset);
    offset += 4;

    occurenceCount = dataBuffer.getInt(offset);
    offset += 4;

    firstDow = dataBuffer.getInt(offset);
    offset += 4;

    deletedInstanceCount = dataBuffer.getInt(offset);
    offset += 4;

    // deleted instances
    for (int i = 0; i < deletedInstanceCount; i++) {
      deletedInstances.add(windowsTimeToDateObject(dataBuffer.getInt(offset)));
      offset += 4;
    }

    modifiedInstanceCount = dataBuffer.getInt(offset);
    offset += 4;

    // modified instances
    modifiedInstances = new int[modifiedInstanceCount];

    for (int i = 0; i < modifiedInstanceCount; i++) {
      modifiedInstances[i] = dataBuffer.getInt(offset);
      offset += 4;
    }

    startDate = dataBuffer.getInt(offset);
    offset += 4;

    endDate = dataBuffer.getInt(offset);
    offset += 4;

    offset += 16;

    short exceptionCount = dataBuffer.getShort(offset);
    offset += 2;
    exceptionInfo = new ArrayList<ExceptionInfo>(exceptionCount);
    for (int i = 0; i < exceptionCount; i++) {
      ExceptionInfo tmpExceptionInfo = new ExceptionInfo(offset);
      exceptionInfo.add(tmpExceptionInfo);
      offset += tmpExceptionInfo.sizeInBytes();

      CalendarService.BusyStatusEnum status = tmpExceptionInfo.getBusyStatus();
      Date startTime = tmpExceptionInfo.getStartDate();
      Date endTime = tmpExceptionInfo.getEndDate();
      if (status == CalendarService.BusyStatusEnum.FREE || startTime == null || endTime == null)
        continue;
      Date currentTime = new Date();

      if (endTime.before(currentTime) || endTime.equals(currentTime)) return;

      boolean executeNow = false;

      if (startTime.before(currentTime) || startTime.equals(currentTime)) executeNow = true;

      CalendarItemTimerTask task =
          new CalendarItemTimerTask(
              status, startTime, endTime, sourceTask.getId(), executeNow, this);

      task.scheduleTasks();
    }
  }

  /**
   * Converts windows time in minutes from 1/1/1601 to <tt>Date</tt> object.
   *
   * @param time the number of minutes from 1/1/1601
   * @return the <tt>Date</tt> object
   */
  public static Date windowsTimeToDateObject(long time) {
    // Date.parse("1/1/1601") == 11644473600000L
    long date = time * 60000 - 11644473600000L;
    date -= TimeZone.getDefault().getOffset(date);
    return new Date(date);
  }

  /** Prints the properties of the class for debugging purpose. */
  @Override
  public String toString() {
    String result = "";
    result += "recurFrequency: " + String.format("%#02x", this.recurFrequency) + "\n";
    result += "patternType: " + String.format("%#02x", this.patternType.getValue()) + "\n";
    result += "calendarType: " + String.format("%#02x", this.calendarType) + "\n";
    result += "endType: " + String.format("%#04x", this.endType) + "\n";

    result += "period: " + this.period + "\n";
    result += "occurenceCount: " + String.format("%#04x", this.occurenceCount) + "\n";
    result += "patternSpecific1: " + String.format("%#04x", this.patternSpecific1) + "\n";
    result += "patternSpecific2: " + String.format("%#04x", this.patternSpecific2) + "\n";
    result += "startDate hex: " + String.format("%#04x", this.startDate) + "\n";
    result += "endDate hex: " + String.format("%#04x", this.endDate) + "\n";

    result +=
        "startDate: "
            + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                .format(windowsTimeToDateObject(this.startDate))
            + "\n";
    result +=
        "endDate: "
            + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                .format(windowsTimeToDateObject(this.endDate))
            + "\n";

    for (int i = 0; i < modifiedInstanceCount; i++) {
      result +=
          "modified Instance date hex: " + String.format("%#04x", this.modifiedInstances[i]) + "\n";

      result +=
          "modified Instance date: "
              + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z")
                  .format(windowsTimeToDateObject(this.modifiedInstances[i]))
              + "\n";
    }

    for (int i = 0; i < deletedInstanceCount; i++) {
      result +=
          "deleted Instance date: "
              + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(deletedInstances.get(i))
              + "\n";
    }
    result += "patternSpecific2: " + String.format("%#04x", this.patternSpecific2) + "\n";

    result += "\n\n =====================Exeptions====================\n\n";

    for (ExceptionInfo info : exceptionInfo) {
      result += info.toString() + "\n\n";
    }
    return result;
  }

  /**
   * Checks whether the given date is in the recurrent pattern range or not
   *
   * @param date the date
   * @return <tt>true</tt> if the date is in the pattern range.
   */
  private boolean dateOutOfRange(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    if ((endType != 0x00002023)
        && (endType != 0xFFFFFFFF)
        && cal.getTime().after(windowsTimeToDateObject(this.endDate))) {
      return true; // the series are finished
    }
    return false;
  }

  /**
   * Calculates and creates the next calendar item.
   *
   * @param previousStartDate the start date of the previous occurrence.
   * @param previousEndDate the end date of the previous occurrence.
   * @return the new calendar item or null if there are no more calendar items from that recurrent
   *     series.
   */
  public CalendarItemTimerTask next(Date previousStartDate, Date previousEndDate) {
    if (dateOutOfRange(new Date())) {
      return null;
    }
    Date startDate = previousStartDate;
    Date endDate = null;
    boolean executeNow = false;
    long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime();
    switch (patternType) {
      case Day:
        {
          startDate = new Date(startDate.getTime() + period * 60000);
          endDate = new Date(previousEndDate.getTime() + period * 60000);
          Date currentDate = new Date();
          if (endDate.before(currentDate)) {
            long offset = currentDate.getTime() - endDate.getTime();
            offset -= offset % (period * 60000);
            if (endDate.getTime() + offset < currentDate.getTime()) {
              offset += period * 60000;
            }

            startDate = new Date(startDate.getTime() + offset);
          }

          Calendar cal = Calendar.getInstance();
          cal.setTime(startDate);
          Calendar cal2 = (Calendar) cal.clone();
          cal.set(Calendar.HOUR_OF_DAY, 0);
          cal.set(Calendar.MINUTE, 0);
          cal.set(Calendar.SECOND, 0);
          cal.set(Calendar.MILLISECOND, 0);
          while (deletedInstances.contains(cal.getTime())) {
            cal.add(Calendar.MINUTE, period);
            cal2.add(Calendar.MINUTE, period);
          }

          if (dateOutOfRange(cal.getTime())) {
            return null;
          }
          startDate = cal2.getTime();
          endDate = new Date(startDate.getTime() + duration);
          if (startDate.before(currentDate)) {
            executeNow = true;
          }

          return new CalendarItemTimerTask(
              sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this);
        }
      case Week:
        {
          Calendar cal = Calendar.getInstance();
          /** The enum for the firstDow field is the same as Calendar day of week enum + 1 day */
          cal.setFirstDayOfWeek(firstDow + 1);
          cal.setTime(startDate);
          int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
          int index = allowedDaysOfWeek.indexOf(dayOfWeek);
          if (++index < allowedDaysOfWeek.size()) {
            cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index));
            startDate = cal.getTime();
            endDate = new Date(startDate.getTime() + duration);
          } else {
            cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(0));
            cal.add(Calendar.WEEK_OF_YEAR, period);
            startDate = cal.getTime();
            endDate = new Date(startDate.getTime() + duration);
          }
          Date currentDate = new Date();
          if (endDate.before(currentDate)) {
            cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(0));
            endDate = new Date(cal.getTimeInMillis() + duration);
            long offset = (currentDate.getTime() - endDate.getTime());

            // 1 week = 604800000 is milliseconds
            offset -= offset % (period * 604800000);
            if (endDate.getTime() + offset < currentDate.getTime()) {
              cal.add(Calendar.WEEK_OF_YEAR, (int) (offset / (period * 604800000)));
              int i = 1;
              while (((cal.getTimeInMillis() + duration) < (currentDate.getTime()))) {
                if (i == allowedDaysOfWeek.size()) {
                  cal.add(Calendar.WEEK_OF_YEAR, period);
                  i = 0;
                }
                cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(i));
                i++;
              }

              startDate = cal.getTime();
            } else {
              startDate = new Date(cal.getTimeInMillis() + offset);
            }
          }

          cal.setTime(startDate);
          Calendar cal2 = (Calendar) cal.clone();
          cal.set(Calendar.HOUR_OF_DAY, 0);
          cal.set(Calendar.MINUTE, 0);
          cal.set(Calendar.SECOND, 0);
          cal.set(Calendar.MILLISECOND, 0);
          dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
          index = allowedDaysOfWeek.indexOf(dayOfWeek) + 1;
          while (deletedInstances.contains(cal.getTime())) {
            if (index >= allowedDaysOfWeek.size()) {
              index = 0;
              cal.add(Calendar.WEEK_OF_YEAR, period);
              cal2.add(Calendar.WEEK_OF_YEAR, period);
            }
            cal.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index));
            cal2.set(Calendar.DAY_OF_WEEK, allowedDaysOfWeek.get(index));
            index++;
          }
          startDate = cal2.getTime();
          endDate = new Date(startDate.getTime() + duration);
          if (dateOutOfRange(endDate)) return null;
          if (startDate.before(currentDate)) {
            executeNow = true;
          }

          return new CalendarItemTimerTask(
              sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this);
        }
      case Month:
      case MonthEnd:
      case HjMonth:
      case HjMonthEnd:
        {
          return nextMonth(startDate, endDate, false);
        }
      case MonthNth:
      case HjMonthNth:
        {
          if (patternSpecific1 == 0x7f && patternSpecific2 == 0x05) {
            return nextMonth(startDate, endDate, true);
          }

          return nextMonthN(startDate, endDate);
        }
    }
    return null;
  }

  /**
   * Finds the occurrence of the events in the next months
   *
   * @param cal the calendar object
   * @param lastDay if <tt>true</tt> it will return the last day of the month
   * @param period the number of months to add
   * @return the calendar object with set date
   */
  private Calendar incrementMonths(Calendar cal, boolean lastDay, int period) {
    int dayOfMonth = patternSpecific1;
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.add(Calendar.MONTH, period);
    if (lastDay || (cal.getActualMaximum(Calendar.DAY_OF_MONTH) < dayOfMonth))
      dayOfMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);

    cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
    return cal;
  }

  /**
   * Finds the next occurrence for monthly recurrence.
   *
   * @param startDate the start date of the previous calendar item.
   * @param endDate the end date of the previous calendar item.
   * @param lastDay if <tt>true</tt> we are interested in last day of the month
   * @return the next item
   */
  public CalendarItemTimerTask nextMonth(Date startDate, Date endDate, boolean lastDay) {
    long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime();
    Calendar cal = Calendar.getInstance();
    cal.setTime(startDate);
    cal = incrementMonths(cal, lastDay, period);
    Date currentDate = new Date();
    if (cal.getTimeInMillis() + duration < currentDate.getTime()) {
      Calendar cal2 = Calendar.getInstance();
      cal2.setTime(currentDate);
      int years = cal2.get(Calendar.YEAR) - cal.get(Calendar.YEAR);
      int months = (years * 12) + (cal2.get(Calendar.MONTH) - cal.get(Calendar.MONTH));
      int monthsToAdd = months;
      monthsToAdd -= months % period;
      cal = incrementMonths(cal, lastDay, monthsToAdd);
      if (cal.getTimeInMillis() + duration < currentDate.getTime()) {
        cal = incrementMonths(cal, lastDay, period);
      }
    }

    Calendar cal2 = (Calendar) cal.clone();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    while (deletedInstances.contains(cal.getTime())) {
      cal = incrementMonths(cal, lastDay, period);
      cal2 = incrementMonths(cal2, lastDay, period);
    }

    startDate = cal2.getTime();
    endDate = new Date(startDate.getTime() + duration);
    if (dateOutOfRange(endDate)) {
      return null;
    }
    boolean executeNow = false;
    if (startDate.before(currentDate)) {
      executeNow = true;
    }

    return new CalendarItemTimerTask(
        sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this);
  }

  /**
   * Finds the occurrence of the events in the next months
   *
   * @param startDate the start date if the calendar item
   * @param dayOfWeekInMonth the number of week days occurrences
   * @return the date of the next occurrence
   */
  private Date getMonthNStartDate(Date startDate, int dayOfWeekInMonth) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(startDate);

    if (dayOfWeekInMonth == -1) {
      Date result = null;
      cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, dayOfWeekInMonth);
      for (int day : allowedDaysOfWeek) {
        cal.set(Calendar.DAY_OF_WEEK, day);
        if (result == null || result.before(cal.getTime())) result = cal.getTime();
      }
      return result;
    } else
      while (dayOfWeekInMonth > 0) {
        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        if (allowedDaysOfWeek.contains(dayOfWeek)) dayOfWeekInMonth--;
        if (dayOfWeekInMonth > 0) cal.add(Calendar.DAY_OF_MONTH, 1);
      }
    return cal.getTime();
  }

  /**
   * Finds the next occurrence for monthly Nth recurrence.
   *
   * @param startDate the start date of the previous calendar item.
   * @param endDate the end date of the previous calendar item.
   * @return the next item
   */
  public CalendarItemTimerTask nextMonthN(Date startDate, Date endDate) {
    int dayOfWeekInMonth = (patternSpecific2 == 5 ? -1 : patternSpecific2);
    long duration = sourceTask.getEndDate().getTime() - sourceTask.getStartDate().getTime();
    Calendar cal = Calendar.getInstance();
    cal.setTime(startDate);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.add(Calendar.MONTH, period);
    cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth));
    Date currentDate = new Date();
    if (cal.getTimeInMillis() + duration < currentDate.getTime()) {
      Calendar cal2 = Calendar.getInstance();
      cal2.setTime(currentDate);
      int years = cal2.get(Calendar.YEAR) - cal.get(Calendar.YEAR);
      int months = (years * 12) + (cal2.get(Calendar.MONTH) - cal.get(Calendar.MONTH));
      int monthsToAdd = months;
      monthsToAdd -= months % period;
      cal.set(Calendar.DAY_OF_MONTH, 1);
      cal.add(Calendar.MONTH, monthsToAdd);
      cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth));
      if (cal.getTimeInMillis() + duration < currentDate.getTime()) {
        cal.set(Calendar.DAY_OF_MONTH, 1);
        cal.add(Calendar.MONTH, monthsToAdd);
        cal.setTime(getMonthNStartDate(cal.getTime(), dayOfWeekInMonth));
      }
    }

    Calendar cal2 = (Calendar) cal.clone();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    while (deletedInstances.contains(cal.getTime())) {
      cal.set(Calendar.DAY_OF_MONTH, 1);
      cal.add(Calendar.MONTH, period);
      startDate = null;
      for (int dayOfWeek : allowedDaysOfWeek) {
        cal.set(Calendar.DAY_OF_WEEK, dayOfWeek);
        cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, dayOfWeekInMonth);
        if ((cal.after(startDate) && dayOfWeekInMonth == -1)
            || (cal.before(startDate) && dayOfWeekInMonth != -1)
            || startDate == null) {
          startDate = cal.getTime();
          cal2.set(Calendar.YEAR, cal.get(Calendar.YEAR));
          cal2.set(Calendar.MONTH, cal.get(Calendar.MONTH));
          cal2.set(Calendar.DATE, cal.get(Calendar.DATE));
        }
      }
    }

    startDate = cal2.getTime();
    endDate = new Date(startDate.getTime() + duration);

    if (dateOutOfRange(endDate)) return null;

    boolean executeNow = false;
    if (startDate.before(currentDate)) {
      executeNow = true;
    }

    return new CalendarItemTimerTask(
        sourceTask.getStatus(), startDate, endDate, sourceTask.getId(), executeNow, this);
  }

  /** Represents the exception info structure. */
  public class ExceptionInfo {
    /** The start date of the exception. */
    private final Date startDate;

    /** The end date of the exception. */
    private final Date endDate;

    /** The original start date of the exception. */
    private final Date originalStartDate;

    /** The modified flags of the exception. */
    private final short overrideFlags;

    /** The new busy status of the exception. */
    private CalendarService.BusyStatusEnum busyStatus;

    /** The size of the fixed fields. */
    private int size = 22;

    /**
     * Parses the data of the exception.
     *
     * @param offset the position where the exception starts in the binary data
     */
    public ExceptionInfo(int offset) {
      startDate = windowsTimeToDateObject(dataBuffer.getInt(offset));
      offset += 4;

      endDate = windowsTimeToDateObject(dataBuffer.getInt(offset));
      offset += 4;

      originalStartDate = windowsTimeToDateObject(dataBuffer.getInt(offset));
      offset += 4;

      overrideFlags = dataBuffer.getShort(offset);
      offset += 2;
      int[] fieldMasks = {0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080};
      for (int mask : fieldMasks) {
        if (mask == 0x0020) {
          if ((overrideFlags & mask) != 0) {
            busyStatus =
                CalendarService.BusyStatusEnum.getFromLong((long) dataBuffer.getInt(offset));
          }

          if (busyStatus == null) {
            busyStatus = sourceTask.getStatus();
          }
        }

        if ((overrideFlags & mask) != 0) {
          if (mask == 0x0010 || mask == 0x0001) {
            short size = dataBuffer.getShort(offset + 2);
            offset += size;
            size += size;
          }
          offset += 4;
          size += 4;
        }
      }

      offset += 4;
      int reservedBlockSize = dataBuffer.getShort(offset);
      size += reservedBlockSize;
    }

    /**
     * Returns the size of the exception
     *
     * @return the size of the exception
     */
    public int sizeInBytes() {
      return size;
    }

    /**
     * Returns the start date
     *
     * @return the start date
     */
    public Date getStartDate() {
      return startDate;
    }

    /**
     * Returns the end date
     *
     * @return the end date
     */
    public Date getEndDate() {
      return endDate;
    }

    /**
     * Returns the busy status
     *
     * @return the busy status
     */
    public CalendarService.BusyStatusEnum getBusyStatus() {
      return busyStatus;
    }

    /** Prints the properties of the class for debugging purpose. */
    @Override
    public String toString() {
      String result = "";
      result +=
          "startDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(startDate) + "\n";
      result += "endDate: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(endDate) + "\n";
      result +=
          "originalStartDate: "
              + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(originalStartDate)
              + "\n";
      return result;
    }
  }
}
/**
 * Represents the Gibberish protocol icon. Implements the <tt>ProtocolIcon</tt> interface in order
 * to provide a gibberish logo image in two different sizes.
 *
 * @author Yana Stamcheva
 */
public class ProtocolIconGibberishImpl implements ProtocolIcon {
  private static Logger logger = Logger.getLogger(ProtocolIconGibberishImpl.class);

  private static ResourceManagementService resourcesService;

  /** A hash table containing the protocol icon in different sizes. */
  private static Hashtable<String, byte[]> iconsTable = new Hashtable<String, byte[]>();

  static {
    iconsTable.put(
        ProtocolIcon.ICON_SIZE_16x16,
        getImageInBytes("service.protocol.gibberish.GIBBERISH_16x16"));

    iconsTable.put(
        ProtocolIcon.ICON_SIZE_32x32,
        getImageInBytes("service.protocol.gibberish.GIBBERISH_32x32"));

    iconsTable.put(
        ProtocolIcon.ICON_SIZE_48x48,
        getImageInBytes("service.protocol.gibberish.GIBBERISH_48x48"));

    iconsTable.put(
        ProtocolIcon.ICON_SIZE_64x64,
        getImageInBytes("service.protocol.gibberish.GIBBERISH_64x64"));
  }

  /** A hash table containing the path to the protocol icon in different sizes. */
  private static Hashtable<String, String> iconPathsTable = new Hashtable<String, String>();

  static {
    iconPathsTable.put(
        ProtocolIcon.ICON_SIZE_16x16,
        getResources().getImagePath("service.protocol.gibberish.GIBBERISH_16x16"));

    iconPathsTable.put(
        ProtocolIcon.ICON_SIZE_32x32,
        getResources().getImagePath("service.protocol.gibberish.GIBBERISH_32x32"));

    iconPathsTable.put(
        ProtocolIcon.ICON_SIZE_48x48,
        getResources().getImagePath("service.protocol.gibberish.GIBBERISH_48x48"));

    iconPathsTable.put(
        ProtocolIcon.ICON_SIZE_64x64,
        getResources().getImagePath("service.protocol.gibberish.GIBBERISH_64x64"));
  }

  /**
   * Implements the <tt>ProtocolIcon.getSupportedSizes()</tt> method. Returns an iterator to a set
   * containing the supported icon sizes.
   *
   * @return an iterator to a set containing the supported icon sizes
   */
  public Iterator<String> getSupportedSizes() {
    return iconsTable.keySet().iterator();
  }

  /** Returne TRUE if a icon with the given size is supported, FALSE-otherwise. */
  public boolean isSizeSupported(String iconSize) {
    return iconsTable.containsKey(iconSize);
  }

  /**
   * Returns the icon image in the given size.
   *
   * @param iconSize the icon size; one of ICON_SIZE_XXX constants
   */
  public byte[] getIcon(String iconSize) {
    return iconsTable.get(iconSize);
  }

  /**
   * Returns a path to the icon with the given size.
   *
   * @param iconSize the size of the icon we're looking for
   * @return the path to the icon with the given size
   */
  public String getIconPath(String iconSize) {
    return iconPathsTable.get(iconSize);
  }

  /**
   * Returns the icon image used to represent the protocol connecting state.
   *
   * @return the icon image used to represent the protocol connecting state
   */
  public byte[] getConnectingIcon() {
    return getImageInBytes("gibberishOnlineIcon");
  }

  /**
   * Returns the byte representation of the image corresponding to the given identifier.
   *
   * @param imageID the identifier of the image
   * @return the byte representation of the image corresponding to the given identifier.
   */
  private static byte[] getImageInBytes(String imageID) {
    InputStream in = getResources().getImageInputStream(imageID);

    if (in == null) return null;
    byte[] image = null;
    try {
      image = new byte[in.available()];

      in.read(image);
    } catch (IOException e) {
      logger.error("Failed to load image:" + imageID, e);
    }

    return image;
  }

  public static ResourceManagementService getResources() {
    if (resourcesService == null) {
      ServiceReference serviceReference =
          GibberishActivator.bundleContext.getServiceReference(
              ResourceManagementService.class.getName());

      if (serviceReference == null) return null;

      resourcesService =
          (ResourceManagementService) GibberishActivator.bundleContext.getService(serviceReference);
    }

    return resourcesService;
  }
}
/**
 * The ICQ protocol filetransfer OperationSet.
 *
 * @author Anthony Schmitt
 * @author Damian Minkov
 */
public class OperationSetFileTransferIcqImpl
    implements OperationSetFileTransfer, RvConnectionManagerListener {
  private static final Logger logger = Logger.getLogger(OperationSetFileTransferIcqImpl.class);

  /** A call back to the ICQ provider that created us. */
  private ProtocolProviderServiceIcqImpl icqProvider = null;

  /** A list of listeners registered for file transfer events. */
  private ArrayList<FileTransferListener> fileTransferListeners =
      new ArrayList<FileTransferListener>();

  /**
   * Create a new FileTransfer OperationSet over the specified Icq provider
   *
   * @param icqProvider ICQ protocol provider service
   */
  public OperationSetFileTransferIcqImpl(ProtocolProviderServiceIcqImpl icqProvider) {
    this.icqProvider = icqProvider;

    icqProvider.addRegistrationStateChangeListener(new RegistrationStateListener());
  }

  /**
   * Sends a file transfer request to the given <tt>toContact</tt> by specifying the local and
   * remote file path and the <tt>fromContact</tt>, sending the file.
   *
   * @param toContact the contact that should receive the file
   * @param file the file to send
   * @return the transfer object
   * @throws IllegalStateException if the protocol provider is not registered or connected
   * @throws IllegalArgumentException if some of the arguments doesn't fit the protocol requirements
   */
  public FileTransfer sendFile(Contact toContact, File file)
      throws IllegalStateException, IllegalArgumentException {
    assertConnected();

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

    // Get the aim connection
    AimConnection aimConnection = icqProvider.getAimConnection();

    // Create an outgoing file transfer instance
    OutgoingFileTransfer outgoingFileTransfer =
        aimConnection
            .getIcbmService()
            .getRvConnectionManager()
            .createOutgoingFileTransfer(new Screenname(toContact.getAddress()));

    String id =
        String.valueOf(outgoingFileTransfer.getRvSessionInfo().getRvSession().getRvSessionId());

    FileTransferImpl outFileTransfer =
        new FileTransferImpl(outgoingFileTransfer, id, toContact, file, FileTransfer.OUT);

    // Adding the file to the outgoing file transfer
    try {
      outgoingFileTransfer.setSingleFile(new File(file.getPath()));
    } catch (IOException e) {
      if (logger.isDebugEnabled()) logger.debug("Error sending file", e);
      return null;
    }

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

    fireFileTransferCreated(event);

    // Sending the file
    outgoingFileTransfer.sendRequest(new InvitationMessage(""));

    outFileTransfer.fireStatusChangeEvent(FileTransferStatusChangeEvent.PREPARING);

    return outFileTransfer;
  }

  /**
   * Sends a file transfer request to the given <tt>toContact</tt> by specifying the local and
   * remote file path and the <tt>fromContact</tt>, sending the file.
   *
   * @param toContact the contact that should receive the file
   * @param fromContact the contact sending the file
   * @param remotePath the remote file path
   * @param localPath the local file path
   * @return the transfer object
   * @throws IllegalStateException if the protocol provider is not registered or connected
   * @throws IllegalArgumentException if some of the arguments doesn't fit the protocol requirements
   */
  public FileTransfer sendFile(
      Contact toContact, Contact fromContact, String remotePath, String localPath)
      throws IllegalStateException, IllegalArgumentException {
    return this.sendFile(toContact, new File(localPath));
  }

  /**
   * Adds the given <tt>FileTransferListener</tt> that would listen for file transfer requests and
   * created file transfers.
   *
   * @param listener the <tt>FileTransferListener</tt> to add
   */
  public void addFileTransferListener(FileTransferListener listener) {
    synchronized (fileTransferListeners) {
      if (!fileTransferListeners.contains(listener)) {
        this.fileTransferListeners.add(listener);
      }
    }
  }

  /**
   * Removes the given <tt>FileTransferListener</tt> that listens for file transfer requests and
   * created file transfers.
   *
   * @param listener the <tt>FileTransferListener</tt> to remove
   */
  public void removeFileTransferListener(FileTransferListener listener) {
    synchronized (fileTransferListeners) {
      this.fileTransferListeners.remove(listener);
    }
  }

  /**
   * 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 (icqProvider == null)
      throw new IllegalStateException(
          "The provider must be non-null and signed on the "
              + "service before being able to send a file.");
    else if (!icqProvider.isRegistered())
      throw new IllegalStateException(
          "The provider must be signed on the service before " + "being able to send a file.");
  }

  /**
   * Function called when a icq file transfer request arrive
   *
   * @param manager the joustsim manager
   * @param transfer the incoming transfer
   */
  public void handleNewIncomingConnection(
      RvConnectionManager manager, IncomingRvConnection transfer) {
    if (transfer instanceof IncomingFileTransfer) {
      if (logger.isTraceEnabled())
        logger.trace("Incoming Icq file transfer request " + transfer.getClass());

      if (!(transfer instanceof IncomingFileTransfer)) {
        logger.warn("Wrong file transfer.");
        return;
      }

      OperationSetPersistentPresenceIcqImpl opSetPersPresence =
          (OperationSetPersistentPresenceIcqImpl)
              icqProvider.getOperationSet(OperationSetPersistentPresence.class);

      Contact sender =
          opSetPersPresence.findContactByID(transfer.getBuddyScreenname().getFormatted());

      IncomingFileTransfer incomingFileTransfer = (IncomingFileTransfer) transfer;

      final Date newDate = new Date();
      final IncomingFileTransferRequest req =
          new IncomingFileTransferRequestIcqImpl(
              icqProvider, this, incomingFileTransfer, sender, newDate);

      // this handels when we receive request and before accept or decline
      // it we receive cancel
      transfer.addEventListener(
          new RvConnectionEventListener() {
            public void handleEventWithStateChange(
                RvConnection transfer, RvConnectionState state, RvConnectionEvent event) {
              if (state == FileTransferState.FAILED && event instanceof BuddyCancelledEvent) {
                fireFileTransferRequestCanceled(
                    new FileTransferRequestEvent(
                        OperationSetFileTransferIcqImpl.this, req, newDate));
              }
            }

            public void handleEvent(RvConnection arg0, RvConnectionEvent arg1) {}
          });

      fireFileTransferRequest(new FileTransferRequestEvent(this, req, newDate));
    }
  }

  /**
   * Delivers the specified event to all registered file transfer listeners.
   *
   * @param event the <tt>EventObject</tt> that we'd like delivered to all registered file transfer
   *     listeners.
   */
  private void fireFileTransferRequest(FileTransferRequestEvent event) {
    Iterator<FileTransferListener> listeners = null;
    synchronized (fileTransferListeners) {
      listeners = new ArrayList<FileTransferListener>(fileTransferListeners).iterator();
    }

    while (listeners.hasNext()) {
      FileTransferListener listener = listeners.next();

      listener.fileTransferRequestReceived(event);
    }
  }

  /**
   * Delivers the specified event to all registered file transfer listeners.
   *
   * @param event the <tt>EventObject</tt> that we'd like delivered to all registered file transfer
   *     listeners.
   */
  void fireFileTransferRequestRejected(FileTransferRequestEvent event) {
    Iterator<FileTransferListener> listeners = null;
    synchronized (fileTransferListeners) {
      listeners = new ArrayList<FileTransferListener>(fileTransferListeners).iterator();
    }

    while (listeners.hasNext()) {
      FileTransferListener listener = listeners.next();

      listener.fileTransferRequestRejected(event);
    }
  }

  /**
   * Delivers the specified event to all registered file transfer listeners.
   *
   * @param event the <tt>EventObject</tt> that we'd like delivered to all registered file transfer
   *     listeners.
   */
  void fireFileTransferRequestCanceled(FileTransferRequestEvent event) {
    Iterator<FileTransferListener> listeners = null;
    synchronized (fileTransferListeners) {
      listeners = new ArrayList<FileTransferListener>(fileTransferListeners).iterator();
    }

    while (listeners.hasNext()) {
      FileTransferListener listener = listeners.next();

      listener.fileTransferRequestCanceled(event);
    }
  }

  /**
   * Delivers the file transfer to all registered listeners.
   *
   * @param event the <tt>FileTransferEvent</tt> that we'd like delivered to all registered file
   *     transfer listeners.
   */
  void fireFileTransferCreated(FileTransferCreatedEvent event) {
    Iterator<FileTransferListener> listeners = null;
    synchronized (fileTransferListeners) {
      listeners = new ArrayList<FileTransferListener>(fileTransferListeners).iterator();
    }

    while (listeners.hasNext()) {
      FileTransferListener listener = listeners.next();
      listener.fileTransferCreated(event);
    }
  }

  /**
   * Returns the maximum file length supported by the protocol in bytes. Supports up to 2GB.
   *
   * @return the file length that is supported.
   */
  public long getMaximumFileLength() {
    return 2147483648l; // = 2048*1024*1024;
  }

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

      if (evt.getNewState() == RegistrationState.REGISTERED) {
        AimConnection aimConnection = icqProvider.getAimConnection();
        aimConnection
            .getIcbmService()
            .getRvConnectionManager()
            .addConnectionManagerListener(OperationSetFileTransferIcqImpl.this);
      }
    }
  }
}
/**
 * The <tt>MUCServiceImpl</tt> class implements the service for the chat rooms.
 *
 * @author Hristo Terezov
 */
public class MUCServiceImpl extends MUCService {

  /** The list of persistent chat rooms. */
  private final ChatRoomListImpl chatRoomList = new ChatRoomListImpl();

  /**
   * The <tt>Logger</tt> used by the <tt>MUCServiceImpl</tt> class and its instances for logging
   * output.
   */
  private static Logger logger = Logger.getLogger(MUCServiceImpl.class);

  /**
   * Called to accept an incoming invitation. Adds the invitation chat room to the list of chat
   * rooms and joins it.
   *
   * @param invitation the invitation to accept.
   */
  public void acceptInvitation(ChatRoomInvitation invitation) {
    ChatRoom chatRoom = invitation.getTargetChatRoom();
    byte[] password = invitation.getChatRoomPassword();

    String nickName = chatRoom.getParentProvider().getAccountID().getUserID();

    joinChatRoom(chatRoom, nickName, password);
  }

  /**
   * Adds a change listener to the <tt>ChatRoomList</tt>.
   *
   * @param l the listener.
   */
  public void addChatRoomListChangeListener(ChatRoomListChangeListener l) {
    chatRoomList.addChatRoomListChangeListener(l);
  }

  /**
   * Removes a change listener to the <tt>ChatRoomList</tt>.
   *
   * @param l the listener.
   */
  public void removeChatRoomListChangeListener(ChatRoomListChangeListener l) {
    chatRoomList.removeChatRoomListChangeListener(l);
  }

  /**
   * Fires a <tt>ChatRoomListChangedEvent</tt> event.
   *
   * @param chatRoomWrapper the chat room.
   * @param eventID the id of the event.
   */
  public void fireChatRoomListChangedEvent(ChatRoomWrapper chatRoomWrapper, int eventID) {
    chatRoomList.fireChatRoomListChangedEvent(chatRoomWrapper, eventID);
  }

  /**
   * Joins the given chat room with the given password and manages all the exceptions that could
   * occur during the join process.
   *
   * @param chatRoomWrapper the chat room to join.
   * @param nickName the nickname we choose for the given chat room.
   * @param password the password.
   * @param rememberPassword if true the password should be saved.
   * @param isFirstAttempt is this the first attempt to join room, used to check whether to show
   *     some error messages
   * @param subject the subject which will be set to the room after the user join successful.
   */
  private void joinChatRoom(
      ChatRoomWrapper chatRoomWrapper,
      String nickName,
      byte[] password,
      boolean rememberPassword,
      boolean isFirstAttempt,
      String subject) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.WARNING"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_CONNECTED",
                      new String[] {chatRoomWrapper.getChatRoomName()}));
      return;
    }

    new JoinChatRoomTask(
            (ChatRoomWrapperImpl) chatRoomWrapper,
            nickName,
            password,
            rememberPassword,
            isFirstAttempt,
            subject)
        .start();
  }

  /**
   * Joins the given chat room with the given password and manages all the exceptions that could
   * occur during the join process.
   *
   * @param chatRoomWrapper the chat room to join.
   * @param nickName the nickname we choose for the given chat room.
   * @param password the password.
   */
  public void joinChatRoom(ChatRoomWrapper chatRoomWrapper, String nickName, byte[] password) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.WARNING"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_CONNECTED",
                      new String[] {chatRoomWrapper.getChatRoomName()}));
      return;
    }

    new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, nickName, password).start();
  }

  /**
   * Joins the given chat room with the given password and manages all the exceptions that could
   * occur during the join process.
   *
   * @param chatRoomWrapper the chat room to join.
   * @param nickName the nickname we choose for the given chat room.
   * @param password the password.
   * @param subject the subject which will be set to the room after the user join successful.
   */
  public void joinChatRoom(
      ChatRoomWrapper chatRoomWrapper, String nickName, byte[] password, String subject) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.WARNING"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_CONNECTED",
                      new String[] {chatRoomWrapper.getChatRoomName()}));

      return;
    }

    // join from add chat room dialog

    new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, nickName, password, subject)
        .start();
  }

  /**
   * Join chat room.
   *
   * @param chatRoomWrapper
   */
  public void joinChatRoom(ChatRoomWrapper chatRoomWrapper) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.WARNING"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_CONNECTED",
                      new String[] {chatRoomWrapper.getChatRoomName()}));

      return;
    }

    new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, null, null).start();
  }

  /**
   * Joins the given chat room and manages all the exceptions that could occur during the join
   * process.
   *
   * @param chatRoom the chat room to join
   * @param nickname the nickname we're using to join
   * @param password the password we're using to join
   */
  public void joinChatRoom(ChatRoom chatRoom, String nickname, byte[] password) {
    ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

    if (chatRoomWrapper == null) {
      ChatRoomProviderWrapper parentProvider =
          chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider());

      chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom);

      chatRoomList.addChatRoom(chatRoomWrapper);

      fireChatRoomListChangedEvent(chatRoomWrapper, ChatRoomListChangeEvent.CHAT_ROOM_ADDED);
    }

    this.joinChatRoom(chatRoomWrapper, nickname, password);
  }

  /**
   * Joins the room with the given name though the given chat room provider.
   *
   * @param chatRoomName the name of the room to join.
   * @param chatRoomProvider the chat room provider to join through.
   */
  public void joinChatRoom(String chatRoomName, ChatRoomProviderWrapper chatRoomProvider) {
    OperationSetMultiUserChat groupChatOpSet =
        chatRoomProvider.getProtocolProvider().getOperationSet(OperationSetMultiUserChat.class);

    ChatRoom chatRoom = null;
    try {
      chatRoom = groupChatOpSet.findRoom(chatRoomName);
    } catch (Exception e) {
      if (logger.isTraceEnabled())
        logger.trace("Un exception occurred while searching for room:" + chatRoomName, e);
    }

    if (chatRoom != null) {
      ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

      if (chatRoomWrapper == null) {
        ChatRoomProviderWrapper parentProvider =
            chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider());

        chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom);

        chatRoomList.addChatRoom(chatRoomWrapper);

        fireChatRoomListChangedEvent(chatRoomWrapper, ChatRoomListChangeEvent.CHAT_ROOM_ADDED);
      }
      joinChatRoom(chatRoomWrapper);
    } else
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.ERROR"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_EXIST",
                      new String[] {
                        chatRoomName,
                        chatRoomProvider.getProtocolProvider().getAccountID().getService()
                      }));
  }

  /**
   * Creates a chat room, by specifying the chat room name, the parent protocol provider and
   * eventually, the contacts invited to participate in this chat room.
   *
   * @param roomName the name of the room
   * @param protocolProvider the parent protocol provider.
   * @param contacts the contacts invited when creating the chat room.
   * @param reason
   * @param persistent is the room persistent
   * @param isPrivate whether the room will be private or public.
   * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room
   */
  public ChatRoomWrapper createChatRoom(
      String roomName,
      ProtocolProviderService protocolProvider,
      Collection<String> contacts,
      String reason,
      boolean persistent,
      boolean isPrivate) {
    return createChatRoom(
        roomName, protocolProvider, contacts, reason, true, persistent, isPrivate);
  }

  /**
   * Creates a chat room, by specifying the chat room name, the parent protocol provider and
   * eventually, the contacts invited to participate in this chat room.
   *
   * @param roomName the name of the room
   * @param protocolProvider the parent protocol provider.
   * @param contacts the contacts invited when creating the chat room.
   * @param reason
   * @param persistent is the room persistent
   * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room
   */
  public ChatRoomWrapper createChatRoom(
      String roomName,
      ProtocolProviderService protocolProvider,
      Collection<String> contacts,
      String reason,
      boolean persistent) {
    return createChatRoom(roomName, protocolProvider, contacts, reason, true, persistent, false);
  }

  /**
   * Creates a chat room, by specifying the chat room name, the parent protocol provider and
   * eventually, the contacts invited to participate in this chat room.
   *
   * @param roomName the name of the room
   * @param protocolProvider the parent protocol provider.
   * @param contacts the contacts invited when creating the chat room.
   * @param reason
   * @param join whether we should join the room after creating it.
   * @param persistent whether the newly created room will be persistent.
   * @param isPrivate whether the room will be private or public.
   * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room
   */
  public ChatRoomWrapper createChatRoom(
      String roomName,
      ProtocolProviderService protocolProvider,
      Collection<String> contacts,
      String reason,
      boolean join,
      boolean persistent,
      boolean isPrivate) {
    ChatRoomWrapper chatRoomWrapper = null;
    OperationSetMultiUserChat groupChatOpSet =
        protocolProvider.getOperationSet(OperationSetMultiUserChat.class);

    // If there's no group chat operation set we have nothing to do here.
    if (groupChatOpSet == null) return null;

    ChatRoom chatRoom = null;
    try {

      HashMap<String, Object> roomProperties = new HashMap<String, Object>();
      roomProperties.put("isPrivate", isPrivate);
      chatRoom = groupChatOpSet.createChatRoom(roomName, roomProperties);

      if (join) {
        chatRoom.join();

        for (String contact : contacts) chatRoom.invite(contact, reason);
      }
    } catch (OperationFailedException ex) {
      logger.error("Failed to create chat room.", ex);

      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.ERROR"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CREATE_CHAT_ROOM_ERROR",
                      new String[] {protocolProvider.getProtocolDisplayName()}),
              ex);
    } catch (OperationNotSupportedException ex) {
      logger.error("Failed to create chat room.", ex);

      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.ERROR"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CREATE_CHAT_ROOM_ERROR",
                      new String[] {protocolProvider.getProtocolDisplayName()}),
              ex);
    }

    if (chatRoom != null) {
      ChatRoomProviderWrapper parentProvider =
          chatRoomList.findServerWrapperFromProvider(protocolProvider);

      // if there is the same room ids don't add new wrapper as old one
      // maybe already created
      chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

      if (chatRoomWrapper == null) {
        chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom);
        chatRoomWrapper.setPersistent(persistent);
        chatRoomList.addChatRoom(chatRoomWrapper);
      }
    }

    return chatRoomWrapper;
  }

  /**
   * Creates a private chat room, by specifying the parent protocol provider and eventually, the
   * contacts invited to participate in this chat room.
   *
   * @param protocolProvider the parent protocol provider.
   * @param contacts the contacts invited when creating the chat room.
   * @param reason
   * @param persistent is the room persistent
   * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room
   */
  public ChatRoomWrapper createPrivateChatRoom(
      ProtocolProviderService protocolProvider,
      Collection<String> contacts,
      String reason,
      boolean persistent) {
    return this.createChatRoom(null, protocolProvider, contacts, reason, persistent, true);
  }

  /**
   * Returns existing chat rooms for the given <tt>chatRoomProvider</tt>.
   *
   * @param chatRoomProvider the <tt>ChatRoomProviderWrapper</tt>, which chat rooms we're looking
   *     for
   * @return existing chat rooms for the given <tt>chatRoomProvider</tt>
   */
  public List<String> getExistingChatRooms(ChatRoomProviderWrapper chatRoomProvider) {
    if (chatRoomProvider == null) return null;

    ProtocolProviderService protocolProvider = chatRoomProvider.getProtocolProvider();

    if (protocolProvider == null) return null;

    OperationSetMultiUserChat groupChatOpSet =
        protocolProvider.getOperationSet(OperationSetMultiUserChat.class);

    if (groupChatOpSet == null) return null;

    List<String> chatRooms = null;
    try {
      chatRooms = groupChatOpSet.getExistingChatRooms();
    } catch (OperationFailedException e) {
      if (logger.isTraceEnabled())
        logger.trace(
            "Failed to obtain existing chat rooms for server: "
                + protocolProvider.getAccountID().getService(),
            e);
    } catch (OperationNotSupportedException e) {
      if (logger.isTraceEnabled())
        logger.trace(
            "Failed to obtain existing chat rooms for server: "
                + protocolProvider.getAccountID().getService(),
            e);
    }

    return chatRooms;
  }

  /**
   * Rejects the given invitation with the specified reason.
   *
   * @param multiUserChatOpSet the operation set to use for rejecting the invitation
   * @param invitation the invitation to reject
   * @param reason the reason for the rejection
   */
  public void rejectInvitation(
      OperationSetMultiUserChat multiUserChatOpSet, ChatRoomInvitation invitation, String reason) {
    multiUserChatOpSet.rejectInvitation(invitation, reason);
  }

  /**
   * Leaves the given chat room.
   *
   * @param chatRoomWrapper the chat room to leave.
   * @return <tt>ChatRoomWrapper</tt> instance associated with the chat room.
   */
  public ChatRoomWrapper leaveChatRoom(ChatRoomWrapper chatRoomWrapper) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      ResourceManagementService resources = MUCActivator.getResources();

      MUCActivator.getAlertUIService()
          .showAlertDialog(
              resources.getI18NString("service.gui.WARNING"),
              resources.getI18NString("service.gui.CHAT_ROOM_LEAVE_NOT_CONNECTED"));

      return null;
    }

    if (chatRoom.isJoined()) chatRoom.leave();

    ChatRoomWrapper existChatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

    if (existChatRoomWrapper == null) return null;

    // We save the choice of the user, before the chat room is really
    // joined, because even the join fails we want the next time when
    // we login to join this chat room automatically.
    ConfigurationUtils.updateChatRoomStatus(
        chatRoomWrapper.getParentProvider().getProtocolProvider(),
        chatRoomWrapper.getChatRoomID(),
        GlobalStatusEnum.OFFLINE_STATUS);

    return existChatRoomWrapper;
  }

  /** Joins a chat room in an asynchronous way. */
  private class JoinChatRoomTask extends Thread {
    private final ChatRoomWrapperImpl chatRoomWrapper;

    private final String nickName;

    private final byte[] password;

    private final boolean rememberPassword;

    private final boolean isFirstAttempt;

    private final String subject;

    private ResourceManagementService resources = MUCActivator.getResources();

    JoinChatRoomTask(
        ChatRoomWrapperImpl chatRoomWrapper,
        String nickName,
        byte[] password,
        boolean rememberPassword,
        boolean isFirstAttempt,
        String subject) {
      this.chatRoomWrapper = chatRoomWrapper;
      this.nickName = nickName;
      this.isFirstAttempt = isFirstAttempt;
      this.subject = subject;

      if (password == null) {
        String passString = chatRoomWrapper.loadPassword();
        if (passString != null) {
          this.password = passString.getBytes();
        } else {
          this.password = null;
        }
      } else {
        this.password = password;
      }
      this.rememberPassword = rememberPassword;
    }

    JoinChatRoomTask(ChatRoomWrapperImpl chatRoomWrapper, String nickName, byte[] password) {
      this(chatRoomWrapper, nickName, password, false, true, null);
    }

    JoinChatRoomTask(
        ChatRoomWrapperImpl chatRoomWrapper, String nickName, byte[] password, String subject) {
      this(chatRoomWrapper, nickName, password, false, true, subject);
    }

    /** @override {@link Thread}{@link #run()} to perform all asynchronous tasks. */
    @Override
    public void run() {
      ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

      try {
        if (password != null && password.length > 0) chatRoom.joinAs(nickName, password);
        else if (nickName != null) chatRoom.joinAs(nickName);
        else chatRoom.join();

        done(JOIN_SUCCESS_PROP);
      } catch (OperationFailedException e) {
        if (logger.isTraceEnabled())
          logger.trace("Failed to join chat room: " + chatRoom.getName(), e);

        switch (e.getErrorCode()) {
          case OperationFailedException.AUTHENTICATION_FAILED:
            done(JOIN_AUTHENTICATION_FAILED_PROP);
            break;
          case OperationFailedException.REGISTRATION_REQUIRED:
            done(JOIN_REGISTRATION_REQUIRED_PROP);
            break;
          case OperationFailedException.PROVIDER_NOT_REGISTERED:
            done(JOIN_PROVIDER_NOT_REGISTERED_PROP);
            break;
          case OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS:
            done(JOIN_SUBSCRIPTION_ALREADY_EXISTS_PROP);
            break;
          default:
            done(JOIN_UNKNOWN_ERROR_PROP);
        }
      }
    }

    /**
     * Performs UI changes after the chat room join task has finished.
     *
     * @param returnCode the result code from the chat room join task.
     */
    private void done(String returnCode) {
      ConfigurationUtils.updateChatRoomStatus(
          chatRoomWrapper.getParentProvider().getProtocolProvider(),
          chatRoomWrapper.getChatRoomID(),
          GlobalStatusEnum.ONLINE_STATUS);

      String errorMessage = null;
      if (JOIN_AUTHENTICATION_FAILED_PROP.equals(returnCode)) {
        chatRoomWrapper.removePassword();

        AuthenticationWindowService authWindowsService =
            ServiceUtils.getService(MUCActivator.bundleContext, AuthenticationWindowService.class);

        AuthenticationWindowService.AuthenticationWindow authWindow =
            authWindowsService.create(
                null,
                null,
                null,
                false,
                chatRoomWrapper.isPersistent(),
                AuthenticationWindow.getAuthenticationWindowIcon(
                    chatRoomWrapper.getParentProvider().getProtocolProvider()),
                resources.getI18NString(
                    "service.gui.AUTHENTICATION_WINDOW_TITLE",
                    new String[] {chatRoomWrapper.getParentProvider().getName()}),
                resources.getI18NString(
                    "service.gui.CHAT_ROOM_REQUIRES_PASSWORD",
                    new String[] {chatRoomWrapper.getChatRoomName()}),
                "",
                null,
                isFirstAttempt
                    ? null
                    : resources.getI18NString(
                        "service.gui.AUTHENTICATION_FAILED",
                        new String[] {chatRoomWrapper.getChatRoomName()}),
                null);

        authWindow.setVisible(true);

        if (!authWindow.isCanceled()) {
          joinChatRoom(
              chatRoomWrapper,
              nickName,
              new String(authWindow.getPassword()).getBytes(),
              authWindow.isRememberPassword(),
              false,
              subject);
        }
      } else if (JOIN_REGISTRATION_REQUIRED_PROP.equals(returnCode)) {
        errorMessage =
            resources.getI18NString(
                "service.gui.CHAT_ROOM_REGISTRATION_REQUIRED",
                new String[] {chatRoomWrapper.getChatRoomName()});
      } else if (JOIN_PROVIDER_NOT_REGISTERED_PROP.equals(returnCode)) {
        errorMessage =
            resources.getI18NString(
                "service.gui.CHAT_ROOM_NOT_CONNECTED",
                new String[] {chatRoomWrapper.getChatRoomName()});
      } else if (JOIN_SUBSCRIPTION_ALREADY_EXISTS_PROP.equals(returnCode)) {
        errorMessage =
            resources.getI18NString(
                "service.gui.CHAT_ROOM_ALREADY_JOINED",
                new String[] {chatRoomWrapper.getChatRoomName()});
      } else {
        errorMessage =
            resources.getI18NString(
                "service.gui.FAILED_TO_JOIN_CHAT_ROOM",
                new String[] {chatRoomWrapper.getChatRoomName()});
      }

      if (!JOIN_SUCCESS_PROP.equals(returnCode)
          && !JOIN_AUTHENTICATION_FAILED_PROP.equals(returnCode)) {
        MUCActivator.getAlertUIService()
            .showAlertPopup(resources.getI18NString("service.gui.ERROR"), errorMessage);
      }

      if (JOIN_SUCCESS_PROP.equals(returnCode)) {
        if (rememberPassword) {
          chatRoomWrapper.savePassword(new String(password));
        }

        if (subject != null) {
          try {
            chatRoomWrapper.getChatRoom().setSubject(subject);
          } catch (OperationFailedException ex) {
            logger.warn("Failed to set subject.");
          }
        }
      }

      chatRoomWrapper.firePropertyChange(returnCode);
    }
  }

  /**
   * Finds the <tt>ChatRoomWrapper</tt> instance associated with the source contact.
   *
   * @param contact the source contact.
   * @return the <tt>ChatRoomWrapper</tt> instance.
   */
  public ChatRoomWrapper findChatRoomWrapperFromSourceContact(SourceContact contact) {
    if (!(contact instanceof ChatRoomSourceContact)) return null;
    ChatRoomSourceContact chatRoomContact = (ChatRoomSourceContact) contact;
    return chatRoomList.findChatRoomWrapperFromChatRoomID(
        chatRoomContact.getChatRoomID(), chatRoomContact.getProvider());
  }

  /**
   * Finds the <tt>ChatRoomWrapper</tt> instance associated with the chat room.
   *
   * @param chatRoomID the id of the chat room.
   * @param pps the provider of the chat room.
   * @return the <tt>ChatRoomWrapper</tt> instance.
   */
  public ChatRoomWrapper findChatRoomWrapperFromChatRoomID(
      String chatRoomID, ProtocolProviderService pps) {
    return chatRoomList.findChatRoomWrapperFromChatRoomID(chatRoomID, pps);
  }

  /**
   * Searches for chat room wrapper in chat room list by chat room.
   *
   * @param chatRoom the chat room.
   * @param create if <tt>true</tt> and the chat room wrapper is not found new chatRoomWrapper is
   *     created.
   * @return found chat room wrapper or the created chat room wrapper.
   */
  @Override
  public ChatRoomWrapper getChatRoomWrapperByChatRoom(ChatRoom chatRoom, boolean create) {
    ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

    if ((chatRoomWrapper == null) && create) {
      ChatRoomProviderWrapper parentProvider =
          chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider());

      chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom);

      chatRoomList.addChatRoom(chatRoomWrapper);
    }
    return chatRoomWrapper;
  }

  /**
   * Goes through the locally stored chat rooms list and for each {@link ChatRoomWrapper} tries to
   * find the corresponding server stored {@link ChatRoom} in the specified operation set. Joins
   * automatically all found chat rooms.
   *
   * @param protocolProvider the protocol provider for the account to synchronize
   * @param opSet the multi user chat operation set, which give us access to chat room server
   */
  public void synchronizeOpSetWithLocalContactList(
      ProtocolProviderService protocolProvider, final OperationSetMultiUserChat opSet) {
    ChatRoomProviderWrapper chatRoomProvider = findServerWrapperFromProvider(protocolProvider);

    if (chatRoomProvider == null) {
      chatRoomProvider = chatRoomList.addRegisteredChatProvider(protocolProvider);
    }

    if (chatRoomProvider != null) {
      chatRoomProvider.synchronizeProvider();
    }
  }

  /**
   * Returns an iterator to the list of chat room providers.
   *
   * @return an iterator to the list of chat room providers.
   */
  public Iterator<ChatRoomProviderWrapper> getChatRoomProviders() {
    return chatRoomList.getChatRoomProviders();
  }

  /**
   * Removes the given <tt>ChatRoom</tt> from the list of all chat rooms.
   *
   * @param chatRoomWrapper the <tt>ChatRoomWrapper</tt> to remove
   */
  public void removeChatRoom(ChatRoomWrapper chatRoomWrapper) {
    chatRoomList.removeChatRoom(chatRoomWrapper);
  }

  /**
   * Destroys the given <tt>ChatRoom</tt> from the list of all chat rooms.
   *
   * @param chatRoomWrapper the <tt>ChatRoomWrapper</tt> to be destroyed.
   * @param reason the reason for destroying.
   * @param alternateAddress the alternate address.
   */
  public void destroyChatRoom(
      ChatRoomWrapper chatRoomWrapper, String reason, String alternateAddress) {
    if (chatRoomWrapper.getChatRoom().destroy(reason, alternateAddress)) {
      MUCActivator.getUIService().closeChatRoomWindow(chatRoomWrapper);
      chatRoomList.removeChatRoom(chatRoomWrapper);
    } else {
      // if we leave a chat room which is not persistent
      // the room can be destroyed on the server, and error is returned
      // when we try to destroy it not-authorized(401)
      if (!chatRoomWrapper.getChatRoom().isPersistent()
          && !chatRoomWrapper.getChatRoom().isJoined()) {
        chatRoomList.removeChatRoom(chatRoomWrapper);
      }
    }
  }

  /**
   * Adds a ChatRoomProviderWrapperListener to the listener list.
   *
   * @param listener the ChatRoomProviderWrapperListener to be added
   */
  public void addChatRoomProviderWrapperListener(ChatRoomProviderWrapperListener listener) {
    chatRoomList.addChatRoomProviderWrapperListener(listener);
  }

  /**
   * Removes the ChatRoomProviderWrapperListener to the listener list.
   *
   * @param listener the ChatRoomProviderWrapperListener to be removed
   */
  public void removeChatRoomProviderWrapperListener(ChatRoomProviderWrapperListener listener) {
    chatRoomList.removeChatRoomProviderWrapperListener(listener);
  }

  /**
   * Returns the <tt>ChatRoomProviderWrapper</tt> that correspond to the given
   * <tt>ProtocolProviderService</tt>. If the list doesn't contain a corresponding wrapper - returns
   * null.
   *
   * @param protocolProvider the protocol provider that we're looking for
   * @return the <tt>ChatRoomProvider</tt> object corresponding to the given
   *     <tt>ProtocolProviderService</tt>
   */
  public ChatRoomProviderWrapper findServerWrapperFromProvider(
      ProtocolProviderService protocolProvider) {
    return chatRoomList.findServerWrapperFromProvider(protocolProvider);
  }

  /**
   * Returns the <tt>ChatRoomWrapper</tt> that correspond to the given <tt>ChatRoom</tt>. If the
   * list of chat rooms doesn't contain a corresponding wrapper - returns null.
   *
   * @param chatRoom the <tt>ChatRoom</tt> that we're looking for
   * @return the <tt>ChatRoomWrapper</tt> object corresponding to the given <tt>ChatRoom</tt>
   */
  public ChatRoomWrapper findChatRoomWrapperFromChatRoom(ChatRoom chatRoom) {
    return chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);
  }

  /**
   * Opens a chat window for the chat room.
   *
   * @param room the chat room.
   */
  public void openChatRoom(ChatRoomWrapper room) {
    if (room.getChatRoom() == null) {
      room =
          createChatRoom(
              room.getChatRoomName(),
              room.getParentProvider().getProtocolProvider(),
              new ArrayList<String>(),
              "",
              false,
              false,
              true);

      // leave the chatroom because getChatRoom().isJoined() returns true
      // otherwise
      if (room.getChatRoom().isJoined()) room.getChatRoom().leave();
    }

    if (!room.getChatRoom().isJoined()) {
      String savedNick =
          ConfigurationUtils.getChatRoomProperty(
              room.getParentProvider().getProtocolProvider(), room.getChatRoomID(), "userNickName");
      String subject = null;

      if (savedNick == null) {
        String[] joinOptions =
            ChatRoomJoinOptionsDialog.getJoinOptions(
                room.getParentProvider().getProtocolProvider(),
                room.getChatRoomID(),
                getDefaultNickname(room.getParentProvider().getProtocolProvider()));
        savedNick = joinOptions[0];
        subject = joinOptions[1];
      }

      if (savedNick != null) {
        joinChatRoom(room, savedNick, null, subject);
      } else return;
    }

    MUCActivator.getUIService().openChatRoomWindow(room);
  }

  /**
   * Returns default nickname for chat room based on the given provider.
   *
   * @param pps the given protocol provider service
   * @return default nickname for chat room based on the given provider.
   */
  public String getDefaultNickname(ProtocolProviderService pps) {
    final OperationSetServerStoredAccountInfo accountInfoOpSet =
        pps.getOperationSet(OperationSetServerStoredAccountInfo.class);

    String displayName = "";
    if (accountInfoOpSet != null) {
      displayName = AccountInfoUtils.getDisplayName(accountInfoOpSet);
    }

    if (displayName == null || displayName.length() == 0) {
      displayName = MUCActivator.getGlobalDisplayDetailsService().getGlobalDisplayName();
      if (displayName == null || displayName.length() == 0) {
        displayName = pps.getAccountID().getUserID();
        if (displayName != null) {
          int atIndex = displayName.lastIndexOf("@");
          if (atIndex > 0) displayName = displayName.substring(0, atIndex);
        }
      }
    }

    return displayName;
  }

  /**
   * Returns instance of the <tt>ServerChatRoomContactSourceService</tt> contact source.
   *
   * @return instance of the <tt>ServerChatRoomContactSourceService</tt> contact source.
   */
  public ContactSourceService getServerChatRoomsContactSourceForProvider(
      ChatRoomProviderWrapper pps) {
    return new ServerChatRoomContactSourceService(pps);
  }

  /**
   * Returns <tt>true</tt> if the contact is <tt>ChatRoomSourceContact</tt>
   *
   * @param contact the contact
   * @return <tt>true</tt> if the contact is <tt>ChatRoomSourceContact</tt>
   */
  public boolean isMUCSourceContact(SourceContact contact) {
    return (contact instanceof ChatRoomSourceContact);
  }
}
/**
 * The ProtocolProviderFactory is what actually creates instances of a ProtocolProviderService
 * implementation. A provider factory would register, persistently store, and remove when necessary,
 * ProtocolProviders. The way things are in the SIP Communicator, a user account is represented (in
 * a 1:1 relationship) by an AccountID and a ProtocolProvider. In other words - one would have as
 * many protocol providers installed in a given moment as they would user account registered through
 * the various services.
 *
 * @author Emil Ivov
 * @author Lubomir Marinov
 */
public abstract class ProtocolProviderFactory {
  /**
   * The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class and its instances for
   * logging output.
   */
  private static final Logger logger = Logger.getLogger(ProtocolProviderFactory.class);

  /** Then name of a property which represents a password. */
  public static final String PASSWORD = "******";

  /**
   * The name of a property representing the name of the protocol for an ProtocolProviderFactory.
   */
  public static final String PROTOCOL = "PROTOCOL_NAME";

  /** The name of a property representing the path to protocol icons. */
  public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH";

  /**
   * The name of a property representing the path to the account icon to be used in the user
   * interface, when the protocol provider service is not available.
   */
  public static final String ACCOUNT_ICON_PATH = "ACCOUNT_ICON_PATH";

  /**
   * The name of a property which represents the AccountID of a ProtocolProvider and that, together
   * with a password is used to login on the protocol network..
   */
  public static final String USER_ID = "USER_ID";

  /** The name that should be displayed to others when we are calling or writing them. */
  public static final String DISPLAY_NAME = "DISPLAY_NAME";

  /** The name that should be displayed to the user on call via and chat via lists. */
  public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_DISPLAY_NAME";

  /** The name of the property under which we store protocol AccountID-s. */
  public static final String ACCOUNT_UID = "ACCOUNT_UID";

  /**
   * The name of the property under which we store protocol the address of a protocol centric entity
   * (any protocol server).
   */
  public static final String SERVER_ADDRESS = "SERVER_ADDRESS";

  /**
   * The name of the property under which we store the number of the port where the server stored
   * against the SERVER_ADDRESS property is expecting connections to be made via this protocol.
   */
  public static final String SERVER_PORT = "SERVER_PORT";

  /**
   * The name of the property under which we store the name of the transport protocol that needs to
   * be used to access the server.
   */
  public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT";

  /** The name of the property under which we store protocol the address of a protocol proxy. */
  public static final String PROXY_ADDRESS = "PROXY_ADDRESS";

  /**
   * The name of the property under which we store the number of the port where the proxy stored
   * against the PROXY_ADDRESS property is expecting connections to be made via this protocol.
   */
  public static final String PROXY_PORT = "PROXY_PORT";

  /**
   * The name of the property which defines whether proxy is auto configured by the protocol by
   * using known methods such as specific DNS queries.
   */
  public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG";

  /** The property indicating the preferred UDP and TCP port to bind to for clear communications. */
  public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME =
      "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT";

  /** The property indicating the preferred TLS (TCP) port to bind to for secure communications. */
  public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME =
      "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT";

  /**
   * The name of the property under which we store the the type of the proxy stored against the
   * PROXY_ADDRESS property. Exact type values depend on protocols and among them are socks4,
   * socks5, http and possibly others.
   */
  public static final String PROXY_TYPE = "PROXY_TYPE";

  /**
   * The name of the property under which we store the the username for the proxy stored against the
   * PROXY_ADDRESS property.
   */
  public static final String PROXY_USERNAME = "******";

  /**
   * The name of the property under which we store the the authorization name for the proxy stored
   * against the PROXY_ADDRESS property.
   */
  public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME";

  /**
   * The name of the property under which we store the password for the proxy stored against the
   * PROXY_ADDRESS property.
   */
  public static final String PROXY_PASSWORD = "******";

  /**
   * The name of the property under which we store the name of the transport protocol that needs to
   * be used to access the proxy.
   */
  public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT";

  /**
   * The name of the property that indicates whether loose routing should be forced for all traffic
   * in an account, rather than routing through an outbound proxy which is the default for Jitsi.
   */
  public static final String FORCE_PROXY_BYPASS = "******";

  /**
   * The name of the property under which we store the user preference for a transport protocol to
   * use (i.e. tcp or udp).
   */
  public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT";

  /**
   * The name of the property under which we store whether we generate resource values or we just
   * use the stored one.
   */
  public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE";

  /**
   * The name of the property under which we store resources such as the jabber resource property.
   */
  public static final String RESOURCE = "RESOURCE";

  /** The name of the property under which we store resource priority. */
  public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY";

  /** The name of the property which defines that the call is encrypted by default */
  public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION";

  /** The name of the property that indicates the encryption protocols for this account. */
  public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL";

  /**
   * The name of the property that indicates the status (enabed or disabled) encryption protocols
   * for this account.
   */
  public static final String ENCRYPTION_PROTOCOL_STATUS = "ENCRYPTION_PROTOCOL_STATUS";

  /** The name of the property which defines if to include the ZRTP attribute to SIP/SDP */
  public static final String DEFAULT_SIPZRTP_ATTRIBUTE = "DEFAULT_SIPZRTP_ATTRIBUTE";

  /**
   * The name of the property which defines the ID of the client TLS certificate configuration
   * entry.
   */
  public static final String CLIENT_TLS_CERTIFICATE = "CLIENT_TLS_CERTIFICATE";

  /**
   * The name of the property under which we store the boolean value indicating if the user name
   * should be automatically changed if the specified name already exists. This property is meant to
   * be used by IRC implementations.
   */
  public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME";

  /**
   * The name of the property under which we store the boolean value indicating if a password is
   * required. Initially this property is meant to be used by IRC implementations.
   */
  public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED";

  /** The name of the property under which we store if the presence is enabled. */
  public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED";

  /** The name of the property under which we store if the p2p mode for SIMPLE should be forced. */
  public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE";

  /**
   * The name of the property under which we store the offline contact polling period for SIMPLE.
   */
  public static final String POLLING_PERIOD = "POLLING_PERIOD";

  /**
   * The name of the property under which we store the chosen default subscription expiration value
   * for SIMPLE.
   */
  public static final String SUBSCRIPTION_EXPIRATION = "SUBSCRIPTION_EXPIRATION";

  /** Indicates if the server address has been validated. */
  public static final String SERVER_ADDRESS_VALIDATED = "SERVER_ADDRESS_VALIDATED";

  /** Indicates if the server settings are over */
  public static final String IS_SERVER_OVERRIDDEN = "IS_SERVER_OVERRIDDEN";
  /** Indicates if the proxy address has been validated. */
  public static final String PROXY_ADDRESS_VALIDATED = "PROXY_ADDRESS_VALIDATED";

  /** Indicates the search strategy chosen for the DICT protocole. */
  public static final String STRATEGY = "STRATEGY";

  /** Indicates a protocol that would not be shown in the user interface as an account. */
  public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN";

  /** Indicates if the given account is the preferred account. */
  public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL";

  /**
   * The name of the property that would indicate if a given account is currently enabled or
   * disabled.
   */
  public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED";

  /** Indicates if ICE should be used. */
  public static final String IS_USE_ICE = "ICE_ENABLED";

  /** Indicates if Google ICE should be used. */
  public static final String IS_USE_GOOGLE_ICE = "GTALK_ICE_ENABLED";

  /** Indicates if STUN server should be automatically discovered. */
  public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN";

  /** Indicates if default STUN server would be used if no other STUN/TURN server are available. */
  public static final String USE_DEFAULT_STUN_SERVER = "USE_DEFAULT_STUN_SERVER";

  /**
   * The name of the boolean account property which indicates whether Jitsi VideoBridge is to be
   * used, if available and supported, for conference calls.
   */
  public static final String USE_JITSI_VIDEO_BRIDGE = "USE_JITSI_VIDEO_BRIDGE";

  /**
   * The property name prefix for all stun server properties. We generally use this prefix in
   * conjunction with an index which is how we store multiple servers.
   */
  public static final String STUN_PREFIX = "STUN";

  /** The base property name for address of additional STUN servers specified. */
  public static final String STUN_ADDRESS = "ADDRESS";

  /** The base property name for port of additional STUN servers specified. */
  public static final String STUN_PORT = "PORT";

  /** The base property name for username of additional STUN servers specified. */
  public static final String STUN_USERNAME = "******";

  /** The base property name for password of additional STUN servers specified. */
  public static final String STUN_PASSWORD = "******";

  /**
   * The base property name for the turn supported property of additional STUN servers specified.
   */
  public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED";

  /** Indicates if JingleNodes should be used with ICE. */
  public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED";

  /** Indicates if JingleNodes should be used with ICE. */
  public static final String AUTO_DISCOVER_JINGLE_NODES = "AUTO_DISCOVER_JINGLE_NODES";

  /** Indicates if JingleNodes should use buddies to search for nodes. */
  public static final String JINGLE_NODES_SEARCH_BUDDIES = "JINGLE_NODES_SEARCH_BUDDIES";

  /** Indicates if UPnP should be used with ICE. */
  public static final String IS_USE_UPNP = "UPNP_ENABLED";

  /** Indicates if we allow non-TLS connection. */
  public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE";

  /** Enable notifications for new voicemail messages. */
  public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED";

  /**
   * Address used to reach voicemail box, by services able to subscribe for voicemail new messages
   * notifications.
   */
  public static final String VOICEMAIL_URI = "VOICEMAIL_URI";

  /** Address used to call to hear your messages stored on the server for your voicemail. */
  public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI";

  /** Indicates if calling is disabled for a certain account. */
  public static final String IS_CALLING_DISABLED_FOR_ACCOUNT = "CALLING_DISABLED";

  /** Indicates if desktop streaming/sharing is disabled for a certain account. */
  public static final String IS_DESKTOP_STREAMING_DISABLED = "DESKTOP_STREAMING_DISABLED";

  /** The sms default server address. */
  public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS";

  /** Keep-alive method used by the protocol. */
  public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD";

  /** The interval for keep-alives if any. */
  public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";

  /** The minimal DTMF tone duration. */
  public static final String DTMF_MINIMAL_TONE_DURATION = "DTMF_MINIMAL_TONE_DURATION";

  /** Paranoia mode when turned on requires all calls to be secure and indicated as such. */
  public static final String MODE_PARANOIA = "MODE_PARANOIA";

  /** The name of the "override encodings" property */
  public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS";

  /** The prefix used to store account encoding properties */
  public static final String ENCODING_PROP_PREFIX = "Encodings";

  /**
   * The <code>BundleContext</code> containing (or to contain) the service registration of this
   * factory.
   */
  private final BundleContext bundleContext;

  /**
   * The name of the protocol this factory registers its <code>ProtocolProviderService</code>s with
   * and to be placed in the properties of the accounts created by this factory.
   */
  private final String protocolName;

  /**
   * The table that we store our accounts in.
   *
   * <p>TODO Synchronize the access to the field which may in turn be better achieved by also hiding
   * it from protected into private access.
   */
  protected final Hashtable<AccountID, ServiceRegistration> registeredAccounts =
      new Hashtable<AccountID, ServiceRegistration>();

  /**
   * The name of the property that indicates the AVP type.
   *
   * <ul>
   *   <li>{@link #SAVP_OFF}
   *   <li>{@link #SAVP_MANDATORY}
   *   <li>{@link #SAVP_OPTIONAL}
   * </ul>
   */
  public static final String SAVP_OPTION = "SAVP_OPTION";

  /** Always use RTP/AVP */
  public static final int SAVP_OFF = 0;

  /** Always use RTP/SAVP */
  public static final int SAVP_MANDATORY = 1;

  /** Sends two media description, with RTP/SAVP being first. */
  public static final int SAVP_OPTIONAL = 2;

  /**
   * The name of the property that defines the enabled SDES cipher suites. Enabled suites are listed
   * as CSV by their RFC name.
   */
  public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES";

  /**
   * Creates a new <tt>ProtocolProviderFactory</tt>.
   *
   * @param bundleContext the bundle context reference of the service
   * @param protocolName the name of the protocol
   */
  protected ProtocolProviderFactory(BundleContext bundleContext, String protocolName) {
    this.bundleContext = bundleContext;
    this.protocolName = protocolName;
  }

  /**
   * Gets the <code>BundleContext</code> containing (or to contain) the service registration of this
   * factory.
   *
   * @return the <code>BundleContext</code> containing (or to contain) the service registration of
   *     this factory
   */
  public BundleContext getBundleContext() {
    return bundleContext;
  }

  /**
   * Initializes and creates an account corresponding to the specified accountProperties and
   * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter. Note
   * that account registration is persistent and accounts that are registered during a particular
   * sip-communicator session would be automatically reloaded during all following sessions until
   * they are removed through the removeAccount method.
   *
   * @param userID the user identifier uniquely representing the newly created account within the
   *     protocol namespace.
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly created account.
   * @throws java.lang.IllegalArgumentException if userID does not correspond to an identifier in
   *     the context of the underlying protocol or if accountProperties does not contain a complete
   *     set of account installation properties.
   * @throws java.lang.IllegalStateException if the account has already been installed.
   * @throws java.lang.NullPointerException if any of the arguments is null.
   */
  public abstract AccountID installAccount(String userID, Map<String, String> accountProperties)
      throws IllegalArgumentException, IllegalStateException, NullPointerException;

  /**
   * Modifies the account corresponding to the specified accountID. This method is meant to be used
   * to change properties of already existing accounts. Note that if the given accountID doesn't
   * correspond to any registered account this method would do nothing.
   *
   * @param protocolProvider the protocol provider service corresponding to the modified account.
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @throws java.lang.NullPointerException if any of the arguments is null.
   */
  public abstract void modifyAccount(
      ProtocolProviderService protocolProvider, Map<String, String> accountProperties)
      throws NullPointerException;

  /**
   * Returns a copy of the list containing the <tt>AccountID</tt>s of all accounts currently
   * registered in this protocol provider.
   *
   * @return a copy of the list containing the <tt>AccountID</tt>s of all accounts currently
   *     registered in this protocol provider.
   */
  public ArrayList<AccountID> getRegisteredAccounts() {
    synchronized (registeredAccounts) {
      return new ArrayList<AccountID>(registeredAccounts.keySet());
    }
  }

  /**
   * Returns the ServiceReference for the protocol provider corresponding to the specified accountID
   * or null if the accountID is unknown.
   *
   * @param accountID the accountID of the protocol provider we'd like to get
   * @return a ServiceReference object to the protocol provider with the specified account id and
   *     null if the account id is unknown to the provider factory.
   */
  public ServiceReference getProviderForAccount(AccountID accountID) {
    ServiceRegistration registration;

    synchronized (registeredAccounts) {
      registration = registeredAccounts.get(accountID);
    }
    return (registration == null) ? null : registration.getReference();
  }

  /**
   * Removes the specified account from the list of accounts that this provider factory is handling.
   * If the specified accountID is unknown to the ProtocolProviderFactory, the call has no effect
   * and false is returned. This method is persistent in nature and once called the account
   * corresponding to the specified ID will not be loaded during future runs of the project.
   *
   * @param accountID the ID of the account to remove.
   * @return true if an account with the specified ID existed and was removed and false otherwise.
   */
  public boolean uninstallAccount(AccountID accountID) {
    // Unregister the protocol provider.
    ServiceReference serRef = getProviderForAccount(accountID);

    boolean wasAccountExisting = false;

    // If the protocol provider service is registered, first unregister the
    // service.
    if (serRef != null) {
      BundleContext bundleContext = getBundleContext();
      ProtocolProviderService protocolProvider =
          (ProtocolProviderService) bundleContext.getService(serRef);

      try {
        protocolProvider.unregister();
      } catch (OperationFailedException ex) {
        logger.error(
            "Failed to unregister protocol provider for account: "
                + accountID
                + " caused by: "
                + ex);
      }
    }

    ServiceRegistration registration;

    synchronized (registeredAccounts) {
      registration = registeredAccounts.remove(accountID);
    }

    // first remove the stored account so when PP is unregistered we can
    // distinguish between deleted or just disabled account
    wasAccountExisting = removeStoredAccount(accountID);

    if (registration != null) {
      // Kill the service.
      registration.unregister();
    }

    return wasAccountExisting;
  }

  /**
   * The method stores the specified account in the configuration service under the package name of
   * the source factory. The restore and remove account methods are to be used to obtain access to
   * and control the stored accounts.
   *
   * <p>In order to store all account properties, the method would create an entry in the
   * configuration service corresponding (beginning with) the <tt>sourceFactory</tt>'s package name
   * and add to it a unique identifier (e.g. the current miliseconds.)
   *
   * @param accountID the AccountID corresponding to the account that we would like to store.
   */
  protected void storeAccount(AccountID accountID) {
    this.storeAccount(accountID, true);
  }

  /**
   * The method stores the specified account in the configuration service under the package name of
   * the source factory. The restore and remove account methods are to be used to obtain access to
   * and control the stored accounts.
   *
   * <p>In order to store all account properties, the method would create an entry in the
   * configuration service corresponding (beginning with) the <tt>sourceFactory</tt>'s package name
   * and add to it a unique identifier (e.g. the current miliseconds.)
   *
   * @param accountID the AccountID corresponding to the account that we would like to store.
   * @param isModification if <tt>false</tt> there must be no such already loaded account, it
   *     <tt>true</tt> ist modification of an existing account. Usually we use this method with
   *     <tt>false</tt> in method installAccount and with <tt>true</tt> or the overridden method in
   *     method modifyAccount.
   */
  protected void storeAccount(AccountID accountID, boolean isModification) {
    if (!isModification && getAccountManager().getStoredAccounts().contains(accountID)) {
      throw new IllegalStateException(
          "An account for id " + accountID.getUserID() + " was already loaded!");
    }

    try {
      getAccountManager().storeAccount(this, accountID);
    } catch (OperationFailedException ofex) {
      throw new UndeclaredThrowableException(ofex);
    }
  }

  /**
   * Saves the password for the specified account after scrambling it a bit so that it is not
   * visible from first sight. (The method remains highly insecure).
   *
   * @param accountID the AccountID for the account whose password we're storing
   * @param password the password itself
   * @throws IllegalArgumentException if no account corresponding to <code>accountID</code> has been
   *     previously stored
   */
  public void storePassword(AccountID accountID, String password) throws IllegalArgumentException {
    try {
      storePassword(getBundleContext(), accountID, password);
    } catch (OperationFailedException ofex) {
      throw new UndeclaredThrowableException(ofex);
    }
  }

  /**
   * Saves the password for the specified account after scrambling it a bit so that it is not
   * visible from first sight (Method remains highly insecure).
   *
   * <p>TODO Delegate the implementation to {@link AccountManager} because it knows the format in
   * which the password (among the other account properties) is to be saved.
   *
   * @param bundleContext a currently valid bundle context.
   * @param accountID the <tt>AccountID</tt> of the account whose password is to be stored
   * @param password the password to be stored
   * @throws IllegalArgumentException if no account corresponding to <tt>accountID</tt> has been
   *     previously stored.
   * @throws OperationFailedException if anything goes wrong while storing the specified
   *     <tt>password</tt>
   */
  protected void storePassword(BundleContext bundleContext, AccountID accountID, String password)
      throws IllegalArgumentException, OperationFailedException {
    String accountPrefix = findAccountPrefix(bundleContext, accountID, getFactoryImplPackageName());

    if (accountPrefix == null) {
      throw new IllegalArgumentException(
          "No previous records found for account ID: "
              + accountID.getAccountUniqueID()
              + " in package"
              + getFactoryImplPackageName());
    }

    CredentialsStorageService credentialsStorage =
        ServiceUtils.getService(bundleContext, CredentialsStorageService.class);

    if (!credentialsStorage.storePassword(accountPrefix, password)) {
      throw new OperationFailedException(
          "CredentialsStorageService failed to storePassword",
          OperationFailedException.GENERAL_ERROR);
    }
  }

  /**
   * Returns the password last saved for the specified account.
   *
   * @param accountID the AccountID for the account whose password we're looking for
   * @return a String containing the password for the specified accountID
   */
  public String loadPassword(AccountID accountID) {
    return loadPassword(getBundleContext(), accountID);
  }

  /**
   * Returns the password last saved for the specified account.
   *
   * <p>TODO Delegate the implementation to {@link AccountManager} because it knows the format in
   * which the password (among the other account properties) was saved.
   *
   * @param bundleContext a currently valid bundle context.
   * @param accountID the AccountID for the account whose password we're looking for..
   * @return a String containing the password for the specified accountID.
   */
  protected String loadPassword(BundleContext bundleContext, AccountID accountID) {
    String accountPrefix = findAccountPrefix(bundleContext, accountID, getFactoryImplPackageName());

    if (accountPrefix == null) return null;

    CredentialsStorageService credentialsStorage =
        ServiceUtils.getService(bundleContext, CredentialsStorageService.class);

    return credentialsStorage.loadPassword(accountPrefix);
  }

  /**
   * Initializes and creates an account corresponding to the specified accountProperties and
   * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter. This
   * method has a persistent effect. Once created the resulting account will remain installed until
   * removed through the uninstallAccount method.
   *
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly loaded account
   */
  public AccountID loadAccount(Map<String, String> accountProperties) {
    AccountID accountID = createAccount(accountProperties);

    loadAccount(accountID);

    return accountID;
  }

  /**
   * Creates a protocol provider for the given <tt>accountID</tt> and registers it in the bundle
   * context. This method has a persistent effect. Once created the resulting account will remain
   * installed until removed through the uninstallAccount method.
   *
   * @param accountID the account identifier
   * @return <tt>true</tt> if the account with the given <tt>accountID</tt> is successfully loaded,
   *     otherwise returns <tt>false</tt>
   */
  public boolean loadAccount(AccountID accountID) {
    // Need to obtain the original user id property, instead of calling
    // accountID.getUserID(), because this method could return a modified
    // version of the user id property.
    String userID = accountID.getAccountPropertyString(ProtocolProviderFactory.USER_ID);

    ProtocolProviderService service = createService(userID, accountID);

    Dictionary<String, String> properties = new Hashtable<String, String>();
    properties.put(PROTOCOL, protocolName);
    properties.put(USER_ID, userID);

    ServiceRegistration serviceRegistration =
        bundleContext.registerService(ProtocolProviderService.class.getName(), service, properties);

    if (serviceRegistration == null) return false;
    else {
      synchronized (registeredAccounts) {
        registeredAccounts.put(accountID, serviceRegistration);
      }
      return true;
    }
  }

  /**
   * Unloads the account corresponding to the given <tt>accountID</tt>. Unregisters the
   * corresponding protocol provider, but keeps the account in contrast to the uninstallAccount
   * method.
   *
   * @param accountID the account identifier
   * @return true if an account with the specified ID existed and was unloaded and false otherwise.
   */
  public boolean unloadAccount(AccountID accountID) {
    // Unregister the protocol provider.
    ServiceReference serRef = getProviderForAccount(accountID);

    if (serRef == null) {
      return false;
    }

    BundleContext bundleContext = getBundleContext();
    ProtocolProviderService protocolProvider =
        (ProtocolProviderService) bundleContext.getService(serRef);

    try {
      protocolProvider.unregister();
    } catch (OperationFailedException ex) {
      logger.error(
          "Failed to unregister protocol provider for account : "
              + accountID
              + " caused by: "
              + ex);
    }

    ServiceRegistration registration;

    synchronized (registeredAccounts) {
      registration = registeredAccounts.remove(accountID);
    }
    if (registration == null) {
      return false;
    }

    // Kill the service.
    registration.unregister();

    return true;
  }

  /**
   * Initializes and creates an account corresponding to the specified accountProperties.
   *
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly created account
   */
  public AccountID createAccount(Map<String, String> accountProperties) {
    BundleContext bundleContext = getBundleContext();
    if (bundleContext == null)
      throw new NullPointerException("The specified BundleContext was null");

    if (accountProperties == null)
      throw new NullPointerException("The specified property map was null");

    String userID = accountProperties.get(USER_ID);
    if (userID == null)
      throw new NullPointerException("The account properties contained no user id.");

    String protocolName = getProtocolName();
    if (!accountProperties.containsKey(PROTOCOL)) accountProperties.put(PROTOCOL, protocolName);

    return createAccountID(userID, accountProperties);
  }

  /**
   * Creates a new <code>AccountID</code> instance with a specific user ID to represent a given set
   * of account properties.
   *
   * <p>The method is a pure factory allowing implementers to specify the runtime type of the
   * created <code>AccountID</code> and customize the instance. The returned <code>AccountID</code>
   * will later be associated with a <code>ProtocolProviderService</code> by the caller (e.g. using
   * {@link #createService(String, AccountID)}).
   *
   * @param userID the user ID of the new instance
   * @param accountProperties the set of properties to be represented by the new instance
   * @return a new <code>AccountID</code> instance with the specified user ID representing the given
   *     set of account properties
   */
  protected abstract AccountID createAccountID(
      String userID, Map<String, String> accountProperties);

  /**
   * Gets the name of the protocol this factory registers its <code>ProtocolProviderService</code>s
   * with and to be placed in the properties of the accounts created by this factory.
   *
   * @return the name of the protocol this factory registers its <code>ProtocolProviderService
   *     </code>s with and to be placed in the properties of the accounts created by this factory
   */
  public String getProtocolName() {
    return protocolName;
  }

  /**
   * Initializes a new <code>ProtocolProviderService</code> instance with a specific user ID to
   * represent a specific <code>AccountID</code>.
   *
   * <p>The method is a pure factory allowing implementers to specify the runtime type of the
   * created <code>ProtocolProviderService</code> and customize the instance. The caller will later
   * register the returned service with the <code>BundleContext</code> of this factory.
   *
   * @param userID the user ID to initialize the new instance with
   * @param accountID the <code>AccountID</code> to be represented by the new instance
   * @return a new <code>ProtocolProviderService</code> instance with the specific user ID
   *     representing the specified <code>AccountID</code>
   */
  protected abstract ProtocolProviderService createService(String userID, AccountID accountID);

  /**
   * Removes the account with <tt>accountID</tt> from the set of accounts that are persistently
   * stored inside the configuration service.
   *
   * @param accountID the AccountID of the account to remove.
   * @return true if an account has been removed and false otherwise.
   */
  protected boolean removeStoredAccount(AccountID accountID) {
    return getAccountManager().removeStoredAccount(this, accountID);
  }

  /**
   * Returns the prefix for all persistently stored properties of the account with the specified id.
   *
   * @param bundleContext a currently valid bundle context.
   * @param accountID the AccountID of the account whose properties we're looking for.
   * @param sourcePackageName a String containing the package name of the concrete factory class
   *     that extends us.
   * @return a String indicating the ConfigurationService property name prefix under which all
   *     account properties are stored or null if no account corresponding to the specified id was
   *     found.
   */
  public static String findAccountPrefix(
      BundleContext bundleContext, AccountID accountID, String sourcePackageName) {
    ServiceReference confReference =
        bundleContext.getServiceReference(ConfigurationService.class.getName());
    ConfigurationService configurationService =
        (ConfigurationService) bundleContext.getService(confReference);

    // first retrieve all accounts that we've registered
    List<String> storedAccounts =
        configurationService.getPropertyNamesByPrefix(sourcePackageName, true);

    // find an account with the corresponding id.
    for (String accountRootPropertyName : storedAccounts) {
      // unregister the account in the configuration service.
      // all the properties must have been registered in the following
      // hierarchy:
      // net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
      String accountUID =
          configurationService.getString(
              accountRootPropertyName // node id
                  + "."
                  + ACCOUNT_UID); // propname

      if (accountID.getAccountUniqueID().equals(accountUID)) {
        return accountRootPropertyName;
      }
    }
    return null;
  }

  /**
   * Returns the name of the package that we're currently running in (i.e. the name of the package
   * containing the proto factory that extends us).
   *
   * @return a String containing the package name of the concrete factory class that extends us.
   */
  private String getFactoryImplPackageName() {
    String className = getClass().getName();

    return className.substring(0, className.lastIndexOf('.'));
  }

  /** Prepares the factory for bundle shutdown. */
  public void stop() {
    if (logger.isTraceEnabled()) logger.trace("Preparing to stop all protocol providers of" + this);

    synchronized (registeredAccounts) {
      for (Enumeration<ServiceRegistration> registrations = registeredAccounts.elements();
          registrations.hasMoreElements(); ) {
        ServiceRegistration reg = registrations.nextElement();

        stop(reg);

        reg.unregister();
      }

      registeredAccounts.clear();
    }
  }

  /**
   * Shuts down the <code>ProtocolProviderService</code> representing an account registered with
   * this factory.
   *
   * @param registeredAccount the <code>ServiceRegistration</code> of the <code>
   *     ProtocolProviderService</code> representing an account registered with this factory
   */
  protected void stop(ServiceRegistration registeredAccount) {
    ProtocolProviderService protocolProviderService =
        (ProtocolProviderService) getBundleContext().getService(registeredAccount.getReference());

    protocolProviderService.shutdown();
  }

  /**
   * Get the <tt>AccountManager</tt> of the protocol.
   *
   * @return <tt>AccountManager</tt> of the protocol
   */
  private AccountManager getAccountManager() {
    BundleContext bundleContext = getBundleContext();
    ServiceReference serviceReference =
        bundleContext.getServiceReference(AccountManager.class.getName());

    return (AccountManager) bundleContext.getService(serviceReference);
  }
}
/**
 * The <tt>AuthenticationWindow</tt> is the window where the user should type his user identifier
 * and password to login.
 *
 * @author Yana Stamcheva
 */
public class AuthenticationWindow extends SIPCommDialog implements ActionListener {
  private static final long serialVersionUID = 1L;

  /** Used for logging. */
  private static Logger logger = Logger.getLogger(AuthenticationWindow.class);

  /** Info text area. */
  private final JTextArea infoTextArea = new JTextArea();

  /** The uin component. */
  private JComponent uinValue;

  /** The password field. */
  private final JPasswordField passwdField = new JPasswordField(15);

  /** The login button. */
  private final JButton loginButton =
      new JButton(DesktopUtilActivator.getResources().getI18NString("service.gui.OK"));

  /** The cancel button. */
  private final JButton cancelButton =
      new JButton(DesktopUtilActivator.getResources().getI18NString("service.gui.CANCEL"));

  /** The check box indicating if the password should be remembered. */
  private final JCheckBox rememberPassCheckBox =
      new SIPCommCheckBox(
          DesktopUtilActivator.getResources().getI18NString("service.gui.REMEMBER_PASSWORD"),
          DesktopUtilActivator.getConfigurationService()
              .getBoolean(PNAME_SAVE_PASSWORD_TICKED, false));

  /**
   * Property to disable/enable allow save password option in authentication window. By default it
   * is enabled.
   */
  private static final String PNAME_ALLOW_SAVE_PASSWORD =
      "******";

  /**
   * Property to set whether the save password option in the authentication window is ticked by
   * default or not. By default it is not ticked
   */
  private static final String PNAME_SAVE_PASSWORD_TICKED =
      "net.java.sip.communicator.util.swing.auth.SAVE_PASSWORD_TICKED";

  /** The name of the server, for which this authentication window is about. */
  private String server;

  /** The user name. */
  private String userName;

  /** The password. */
  private char[] password;

  /** Indicates if the password should be remembered. */
  private boolean isRememberPassword = false;

  /** Indicates if the window has been canceled. */
  private boolean isCanceled = false;

  /** A lock used to synchronize data setting. */
  private final Object lock = new Object();

  /** The condition that decides whether to continue waiting for data. */
  private boolean buttonClicked = false;

  /** Used to override default Authentication window title. */
  private String windowTitle = null;

  /** Used to override default window text. */
  private String windowText = null;

  /** Used to override username label text. */
  private String usernameLabelText = null;

  /** Used to override password label text. */
  private String passwordLabelText = null;

  /** The sign up link if specified. */
  private String signupLink = null;

  /**
   * Creates an instance of the <tt>LoginWindow</tt>.
   *
   * @param server the server name
   * @param isUserNameEditable indicates if the user name is editable
   * @param icon the icon to display on the left of the authentication window
   */
  public AuthenticationWindow(String server, boolean isUserNameEditable, ImageIcon icon) {
    this(null, null, server, isUserNameEditable, false, icon, null, null, null, null, null, null);
  }

  /**
   * Creates an instance of the <tt>LoginWindow</tt>.
   *
   * @param server the server name
   * @param isUserNameEditable indicates if the user name is editable
   * @param icon the icon to display on the left of the authentication window
   * @param windowTitle customized window title
   * @param windowText customized window text
   * @param usernameLabelText customized username field label text
   * @param passwordLabelText customized password field label text
   * @param errorMessage an error message if this dialog is shown to indicate the user that
   *     something went wrong
   * @param signupLink an URL that allows the user to sign up
   */
  private AuthenticationWindow(
      String userName,
      char[] password,
      String server,
      boolean isUserNameEditable,
      boolean isRememberPassword,
      ImageIcon icon,
      String windowTitle,
      String windowText,
      String usernameLabelText,
      String passwordLabelText,
      String errorMessage,
      String signupLink) {
    super(false);

    this.windowTitle = windowTitle;
    this.windowText = windowText;
    this.usernameLabelText = usernameLabelText;
    this.passwordLabelText = passwordLabelText;
    this.isRememberPassword = isRememberPassword;
    this.signupLink = signupLink;

    init(userName, password, server, isUserNameEditable, icon, errorMessage);
  }

  /**
   * Initializes this authentication window.
   *
   * @param server the server
   * @param isUserNameEditable indicates if the user name is editable
   * @param icon the icon to show on the authentication window
   */
  private void init(
      String userName,
      char[] password,
      String server,
      boolean isUserNameEditable,
      ImageIcon icon,
      String errorMessage) {
    this.server = server;

    initIcon(icon);

    if (!isUserNameEditable) this.uinValue = new JLabel();
    else this.uinValue = new JTextField();

    this.init();

    this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    this.enableKeyActions();

    this.setResizable(false);

    /*
     * Workaround for the following bug:
     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4446522
     * Need to pack() the window after it's opened in order to obtain the
     * correct size of our infoTextArea, otherwise window size is wrong and
     * buttons on the south are cut.
     */
    this.addWindowListener(
        new WindowAdapter() {
          public void windowOpened(WindowEvent e) {
            pack();
            removeWindowListener(this);
          }
        });

    if (OSUtils.IS_MAC) getRootPane().putClientProperty("apple.awt.brushMetalLook", Boolean.TRUE);

    if (userName != null) {
      if (uinValue instanceof JLabel) ((JLabel) uinValue).setText(userName);
      else if (uinValue instanceof JTextField) ((JTextField) uinValue).setText(userName);
    }

    if (password != null) passwdField.setText(new String(password));

    if (errorMessage != null) {
      this.infoTextArea.setForeground(Color.RED);
      this.infoTextArea.setText(errorMessage);
    }
  }

  /**
   * Creates an instance of the <tt>LoginWindow</tt>.
   *
   * @param userName the user name to set by default
   * @param password the password to set by default
   * @param server the server name this authentication window is about
   * @param isUserNameEditable indicates if the user name should be editable by the user or not
   * @param icon the icon displayed on the left of the authentication window
   * @param errorMessage an error message explaining a reason for opening the authentication dialog
   *     (when a wrong password was provided, etc.)
   */
  public AuthenticationWindow(
      String userName,
      char[] password,
      String server,
      boolean isUserNameEditable,
      ImageIcon icon,
      String errorMessage) {
    this(
        userName,
        password,
        server,
        isUserNameEditable,
        false,
        icon,
        null,
        null,
        null,
        null,
        errorMessage,
        null);
  }

  /**
   * Creates an instance of the <tt>LoginWindow</tt>.
   *
   * @param userName the user name to set by default
   * @param password the password to set by default
   * @param server the server name this authentication window is about
   * @param isUserNameEditable indicates if the user name should be editable by the user or not
   * @param icon the icon displayed on the left of the authentication window
   * @param errorMessage an error message explaining a reason for opening the authentication dialog
   *     (when a wrong password was provided, etc.)
   * @param signupLink an URL that allows the user to sign up
   */
  public AuthenticationWindow(
      String userName,
      char[] password,
      String server,
      boolean isUserNameEditable,
      ImageIcon icon,
      String errorMessage,
      String signupLink) {
    this(
        userName,
        password,
        server,
        isUserNameEditable,
        false,
        icon,
        null,
        null,
        null,
        null,
        errorMessage,
        signupLink);
  }

  /**
   * Creates an instance of the <tt>LoginWindow</tt>.
   *
   * @param userName the user name to set by default
   * @param password the password to set by default
   * @param server the server name this authentication window is about
   * @param isUserNameEditable indicates if the user name should be editable by the user or not
   * @param icon the icon displayed on the left of the authentication window
   */
  public AuthenticationWindow(
      String userName, char[] password, String server, boolean isUserNameEditable, ImageIcon icon) {
    this(userName, password, server, isUserNameEditable, icon, null, null);
  }

  /**
   * Creates an instance of the <tt>LoginWindow</tt>.
   *
   * @param owner the owner of this dialog
   * @param userName the user name to set by default
   * @param password the password to set by default
   * @param server the server name this authentication window is about
   * @param isUserNameEditable indicates if the user name should be editable by the user or not
   * @param icon the icon displayed on the left of the authentication window
   */
  public AuthenticationWindow(
      Dialog owner,
      String userName,
      char[] password,
      String server,
      boolean isUserNameEditable,
      ImageIcon icon) {
    super(owner, false);

    init(userName, password, server, isUserNameEditable, icon, null);
  }

  /**
   * Shows or hides the "save password" checkbox.
   *
   * @param allow the checkbox is shown when allow is <tt>true</tt>
   */
  public void setAllowSavePassword(boolean allow) {
    rememberPassCheckBox.setVisible(allow);
  }

  /**
   * Initializes the icon image.
   *
   * @param icon the icon to show on the left of the window
   */
  private void initIcon(ImageIcon icon) {
    // If an icon isn't provided set the application logo icon by default.
    if (icon == null)
      icon =
          DesktopUtilActivator.getResources().getImage("service.gui.SIP_COMMUNICATOR_LOGO_64x64");

    JLabel iconLabel = new JLabel(icon);

    iconLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

    iconLabel.setAlignmentY(Component.TOP_ALIGNMENT);

    JPanel iconPanel = new TransparentPanel(new BorderLayout());
    iconPanel.add(iconLabel, BorderLayout.NORTH);

    getContentPane().add(iconPanel, BorderLayout.WEST);
  }

  /** Constructs the <tt>LoginWindow</tt>. */
  private void init() {
    String title;

    if (windowTitle != null) title = windowTitle;
    else
      title =
          DesktopUtilActivator.getResources()
              .getI18NString("service.gui.AUTHENTICATION_WINDOW_TITLE", new String[] {server});

    String text;
    if (windowText != null) text = windowText;
    else
      text =
          DesktopUtilActivator.getResources()
              .getI18NString("service.gui.AUTHENTICATION_REQUESTED_SERVER", new String[] {server});

    String uinText;
    if (usernameLabelText != null) uinText = usernameLabelText;
    else uinText = DesktopUtilActivator.getResources().getI18NString("service.gui.IDENTIFIER");

    String passText;
    if (passwordLabelText != null) passText = passwordLabelText;
    else passText = DesktopUtilActivator.getResources().getI18NString("service.gui.PASSWORD");

    setTitle(title);

    infoTextArea.setEditable(false);
    infoTextArea.setOpaque(false);
    infoTextArea.setLineWrap(true);
    infoTextArea.setWrapStyleWord(true);
    infoTextArea.setFont(infoTextArea.getFont().deriveFont(Font.BOLD));
    infoTextArea.setText(text);
    infoTextArea.setAlignmentX(0.5f);

    JLabel uinLabel = new JLabel(uinText);
    uinLabel.setFont(uinLabel.getFont().deriveFont(Font.BOLD));

    JLabel passwdLabel = new JLabel(passText);
    passwdLabel.setFont(passwdLabel.getFont().deriveFont(Font.BOLD));

    TransparentPanel labelsPanel = new TransparentPanel(new GridLayout(0, 1, 8, 8));

    labelsPanel.add(uinLabel);
    labelsPanel.add(passwdLabel);

    TransparentPanel textFieldsPanel = new TransparentPanel(new GridLayout(0, 1, 8, 8));

    textFieldsPanel.add(uinValue);
    textFieldsPanel.add(passwdField);

    JPanel southFieldsPanel = new TransparentPanel(new GridLayout(1, 2));

    this.rememberPassCheckBox.setOpaque(false);
    this.rememberPassCheckBox.setBorder(null);

    southFieldsPanel.add(rememberPassCheckBox);
    if (signupLink != null && signupLink.length() > 0)
      southFieldsPanel.add(
          createWebSignupLabel(
              DesktopUtilActivator.getResources().getI18NString("plugin.simpleaccregwizz.SIGNUP"),
              signupLink));
    else southFieldsPanel.add(new JLabel());

    boolean allowRememberPassword = true;

    String allowRemPassStr =
        DesktopUtilActivator.getResources().getSettingsString(PNAME_ALLOW_SAVE_PASSWORD);
    if (allowRemPassStr != null) {
      allowRememberPassword = Boolean.parseBoolean(allowRemPassStr);
    }
    allowRememberPassword =
        DesktopUtilActivator.getConfigurationService()
            .getBoolean(PNAME_ALLOW_SAVE_PASSWORD, allowRememberPassword);

    setAllowSavePassword(allowRememberPassword);

    JPanel buttonPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));

    buttonPanel.add(loginButton);
    buttonPanel.add(cancelButton);

    JPanel southEastPanel = new TransparentPanel(new BorderLayout());
    southEastPanel.add(buttonPanel, BorderLayout.EAST);

    TransparentPanel mainPanel = new TransparentPanel(new BorderLayout(10, 10));

    mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 20));

    JPanel mainFieldsPanel = new TransparentPanel(new BorderLayout(0, 10));
    mainFieldsPanel.add(labelsPanel, BorderLayout.WEST);
    mainFieldsPanel.add(textFieldsPanel, BorderLayout.CENTER);
    mainFieldsPanel.add(southFieldsPanel, BorderLayout.SOUTH);

    mainPanel.add(infoTextArea, BorderLayout.NORTH);
    mainPanel.add(mainFieldsPanel, BorderLayout.CENTER);
    mainPanel.add(southEastPanel, BorderLayout.SOUTH);

    this.getContentPane().add(mainPanel, BorderLayout.EAST);

    this.loginButton.setName("ok");
    this.cancelButton.setName("cancel");
    if (loginButton.getPreferredSize().width > cancelButton.getPreferredSize().width)
      cancelButton.setPreferredSize(loginButton.getPreferredSize());
    else loginButton.setPreferredSize(cancelButton.getPreferredSize());

    this.loginButton.setMnemonic(
        DesktopUtilActivator.getResources().getI18nMnemonic("service.gui.OK"));
    this.cancelButton.setMnemonic(
        DesktopUtilActivator.getResources().getI18nMnemonic("service.gui.CANCEL"));

    this.loginButton.addActionListener(this);
    this.cancelButton.addActionListener(this);

    this.getRootPane().setDefaultButton(loginButton);
  }

  /**
   * Handles the <tt>ActionEvent</tt> triggered when one of the buttons is clicked. When "Login"
   * button is chosen installs a new account from the user input and logs in.
   *
   * @param evt the action event that has just occurred.
   */
  public void actionPerformed(ActionEvent evt) {
    JButton button = (JButton) evt.getSource();
    String buttonName = button.getName();

    if ("ok".equals(buttonName)) {
      if (uinValue instanceof JLabel) userName = ((JLabel) uinValue).getText();
      else if (uinValue instanceof JTextField) userName = ((JTextField) uinValue).getText();

      password = passwdField.getPassword();
      isRememberPassword = rememberPassCheckBox.isSelected();
    } else {
      isCanceled = true;
    }

    // release the caller that opened the window
    buttonClicked = true;
    synchronized (lock) {
      lock.notify();
    }

    this.dispose();
  }

  /** Enables the actions when a key is pressed, for now closes the window when esc is pressed. */
  private void enableKeyActions() {
    @SuppressWarnings("serial")
    UIAction act =
        new UIAction() {
          public void actionPerformed(ActionEvent e) {
            close(true);
          }
        };

    getRootPane().getActionMap().put("close", act);

    InputMap imap = this.getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close");
  }

  /**
   * Automatically clicks the cancel button, when this window is closed.
   *
   * @param isEscaped indicates if the window has been closed by pressing the Esc key
   */
  @Override
  protected void close(boolean isEscaped) {
    this.cancelButton.doClick();
  }

  /**
   * Shows this modal dialog.
   *
   * @param isVisible specifies whether we should be showing or hiding the window.
   */
  @Override
  public void setVisible(final boolean isVisible) {
    this.setName("AUTHENTICATION");

    if (getOwner() != null) setModal(true);

    if (isVisible) {
      addWindowFocusListener(
          new WindowAdapter() {
            public void windowGainedFocus(WindowEvent e) {
              removeWindowFocusListener(this);

              if (uinValue instanceof JTextField && "".equals(((JTextField) uinValue).getText())) {
                uinValue.requestFocusInWindow();
              } else passwdField.requestFocusInWindow();
            }
          });
    }

    super.setVisible(isVisible);

    if (isVisible) {
      if (getOwner() != null) return;

      synchronized (lock) {
        while (!buttonClicked) {
          try {
            lock.wait();
          } catch (InterruptedException e) {
          } // we don't care, just retry
        }
      }
    }
  }

  /**
   * Indicates if this window has been canceled.
   *
   * @return <tt>true</tt> if this window has been canceled, <tt>false</tt> - otherwise
   */
  public boolean isCanceled() {
    return isCanceled;
  }

  /**
   * Returns the user name entered by the user or previously set if the user name is not editable.
   *
   * @return the user name
   */
  public String getUserName() {
    return userName;
  }

  /**
   * Returns the password entered by the user.
   *
   * @return the password
   */
  public char[] getPassword() {
    return password;
  }

  /**
   * Indicates if the password should be remembered.
   *
   * @return <tt>true</tt> if the password should be remembered, <tt>false</tt> - otherwise
   */
  public boolean isRememberPassword() {
    return isRememberPassword;
  }

  /**
   * Creates the subscribe label.
   *
   * @param linkName the link name
   * @return the newly created subscribe label
   */
  private Component createWebSignupLabel(String linkName, final String linkURL) {
    JLabel subscribeLabel =
        new JLabel("<html><a href=''>" + linkName + "</a></html>", JLabel.RIGHT);

    subscribeLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
    subscribeLabel.setToolTipText(
        DesktopUtilActivator.getResources()
            .getI18NString("plugin.simpleaccregwizz.SPECIAL_SIGNUP"));
    subscribeLabel.addMouseListener(
        new MouseAdapter() {
          public void mousePressed(MouseEvent e) {
            try {
              DesktopUtilActivator.getBrowserLauncher().openURL(linkURL);
            } catch (UnsupportedOperationException ex) {
              // This should not happen, because we check if the
              // operation is supported, before adding the sign
              // up.
              logger.error("The web sign up is not supported.", ex);
            }
          }
        });
    return subscribeLabel;
  }
}
/**
 * Implements the {@link ReplacementService} to provide previews for direct image links.
 *
 * @author Purvesh Sahoo
 * @author Marin Dzhigarov
 */
public class ReplacementServiceDirectImageImpl implements DirectImageReplacementService {
  /** The logger for this class. */
  private static final Logger logger = Logger.getLogger(ReplacementServiceDirectImageImpl.class);

  /** The regex used to match the link in the message. */
  public static final String URL_PATTERN = "https?\\:\\/\\/(www\\.)*.*\\.(?:jpg|png|gif)";

  /** Configuration label shown in the config form. */
  public static final String DIRECT_IMAGE_CONFIG_LABEL = "Direct Image Link";

  /** Source name; also used as property label. */
  public static final String SOURCE_NAME = "DIRECTIMAGE";

  /** Maximum allowed size of the image in bytes. The default size is 2MB. */
  private long imgMaxSize = 2097152;

  /** Configuration property name for maximum allowed size of the image in bytes. */
  private static final String MAX_IMG_SIZE =
      "net.java.sip.communicator.impl.replacement.directimage.MAX_IMG_SIZE";

  /** Constructor for <tt>ReplacementServiceDirectImageImpl</tt>. */
  public ReplacementServiceDirectImageImpl() {
    setMaxImgSizeFromConf();
    logger.trace("Creating a Direct Image Link Source.");
  }

  /**
   * Gets the max allowed size value in bytes from Configuration service and sets the value to
   * <tt>imgMaxSize</tt> if succeed. If the configuration property isn't available or the value
   * can't be parsed correctly the value of <tt>imgMaxSize</tt> isn't changed.
   */
  private void setMaxImgSizeFromConf() {
    ConfigurationService configService = DirectImageActivator.getConfigService();

    if (configService != null) {
      String confImgSizeStr = (String) configService.getProperty(MAX_IMG_SIZE);
      try {
        if (confImgSizeStr != null) {
          imgMaxSize = Long.parseLong(confImgSizeStr);
        } else {
          configService.setProperty(MAX_IMG_SIZE, imgMaxSize);
        }
      } catch (NumberFormatException e) {
        if (logger.isDebugEnabled())
          logger.debug(
              "Failed to parse max image size: " + confImgSizeStr + ". Going for default.");
      }
    }
  }

  /**
   * Returns the thumbnail URL of the image link provided.
   *
   * @param sourceString the original image link.
   * @return the thumbnail image link; the original link in case of no match.
   */
  public String getReplacement(String sourceString) {
    return sourceString;
  }

  /**
   * Returns the source name
   *
   * @return the source name
   */
  public String getSourceName() {
    return SOURCE_NAME;
  }

  /**
   * Returns the pattern of the source
   *
   * @return the source pattern
   */
  public String getPattern() {
    return URL_PATTERN;
  }

  @Override
  /**
   * Returns the size of the image in bytes.
   *
   * @param sourceString the image link.
   * @return the file size in bytes of the image link provided; -1 if the size isn't available or
   *     exceeds the max allowed image size.
   */
  public int getImageSize(String sourceString) {
    int length = -1;
    try {

      URL url = new URL(sourceString);
      String protocol = url.getProtocol();
      if (protocol.equals("http") || protocol.equals("https")) {
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        length = connection.getContentLength();
        connection.disconnect();
      } else if (protocol.equals("ftp")) {
        FTPUtils ftp = new FTPUtils(sourceString);
        length = ftp.getSize();
        ftp.disconnect();
      }

      if (length > imgMaxSize) {
        length = -1;
      }
    } catch (Exception e) {
      logger.debug("Failed to get the length of the image in bytes", e);
    }
    return length;
  }

  /**
   * Returns true if the content type of the resource pointed by sourceString is an image.
   *
   * @param sourceString the original image link.
   * @return true if the content type of the resource pointed by sourceString is an image.
   */
  @Override
  public boolean isDirectImage(String sourceString) {
    boolean isDirectImage = false;
    try {
      URL url = new URL(sourceString);
      String protocol = url.getProtocol();
      if (protocol.equals("http") || protocol.equals("https")) {
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        isDirectImage = connection.getContentType().contains("image");
        connection.disconnect();
      } else if (protocol.equals("ftp")) {
        if (sourceString.endsWith(".png")
            || sourceString.endsWith(".jpg")
            || sourceString.endsWith(".gif")) {
          isDirectImage = true;
        }
      }
    } catch (Exception e) {
      logger.debug("Failed to retrieve content type information for" + sourceString, e);
    }
    return isDirectImage;
  }
}
Beispiel #30
0
/**
 * @author Lyubomir Marinov
 * @author Damian Minkov
 * @author Yana Stamcheva
 */
public class MediaConfiguration {
  /** The <tt>Logger</tt> used by the <tt>MediaConfiguration</tt> class for logging output. */
  private static final Logger logger = Logger.getLogger(MediaConfiguration.class);

  /** The <tt>MediaService</tt> implementation used by <tt>MediaConfiguration</tt>. */
  private static final MediaServiceImpl mediaService = NeomediaActivator.getMediaServiceImpl();

  /** The preferred width of all panels. */
  private static final int WIDTH = 350;

  /**
   * Indicates if the Devices settings configuration tab should be disabled, i.e. not visible to the
   * user.
   */
  private static final String DEVICES_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.devicesconfig.DISABLED";

  /**
   * Indicates if the Audio/Video encodings configuration tab should be disabled, i.e. not visible
   * to the user.
   */
  private static final String ENCODINGS_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.encodingsconfig.DISABLED";

  /**
   * Indicates if the Video/More Settings configuration tab should be disabled, i.e. not visible to
   * the user.
   */
  private static final String VIDEO_MORE_SETTINGS_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.videomoresettingsconfig.DISABLED";

  /**
   * Returns the audio configuration panel.
   *
   * @return the audio configuration panel
   */
  public static Component createAudioConfigPanel() {
    return createControls(DeviceConfigurationComboBoxModel.AUDIO);
  }

  /**
   * Returns the video configuration panel.
   *
   * @return the video configuration panel
   */
  public static Component createVideoConfigPanel() {
    return createControls(DeviceConfigurationComboBoxModel.VIDEO);
  }

  private static void createAudioPreview(
      final AudioSystem audioSystem,
      final JComboBox comboBox,
      final SoundLevelIndicator soundLevelIndicator) {
    final ActionListener captureComboActionListener =
        new ActionListener() {
          private final SimpleAudioLevelListener audioLevelListener =
              new SimpleAudioLevelListener() {
                public void audioLevelChanged(int level) {
                  soundLevelIndicator.updateSoundLevel(level);
                }
              };

          private AudioMediaDeviceSession deviceSession;

          private final BufferTransferHandler transferHandler =
              new BufferTransferHandler() {
                public void transferData(PushBufferStream stream) {
                  try {
                    stream.read(transferHandlerBuffer);
                  } catch (IOException ioe) {
                  }
                }
              };

          private final Buffer transferHandlerBuffer = new Buffer();

          public void actionPerformed(ActionEvent event) {
            setDeviceSession(null);

            CaptureDeviceInfo cdi;

            if (comboBox == null) {
              cdi = soundLevelIndicator.isShowing() ? audioSystem.getCaptureDevice() : null;
            } else {
              Object selectedItem =
                  soundLevelIndicator.isShowing() ? comboBox.getSelectedItem() : null;

              cdi =
                  (selectedItem instanceof DeviceConfigurationComboBoxModel.CaptureDevice)
                      ? ((DeviceConfigurationComboBoxModel.CaptureDevice) selectedItem).info
                      : null;
            }

            if (cdi != null) {
              for (MediaDevice md : mediaService.getDevices(MediaType.AUDIO, MediaUseCase.ANY)) {
                if (md instanceof AudioMediaDeviceImpl) {
                  AudioMediaDeviceImpl amd = (AudioMediaDeviceImpl) md;

                  if (cdi.equals(amd.getCaptureDeviceInfo())) {
                    try {
                      MediaDeviceSession deviceSession = amd.createSession();
                      boolean setDeviceSession = false;

                      try {
                        if (deviceSession instanceof AudioMediaDeviceSession) {
                          setDeviceSession((AudioMediaDeviceSession) deviceSession);
                          setDeviceSession = true;
                        }
                      } finally {
                        if (!setDeviceSession) deviceSession.close();
                      }
                    } catch (Throwable t) {
                      if (t instanceof ThreadDeath) throw (ThreadDeath) t;
                    }
                    break;
                  }
                }
              }
            }
          }

          private void setDeviceSession(AudioMediaDeviceSession deviceSession) {
            if (this.deviceSession == deviceSession) return;

            if (this.deviceSession != null) {
              try {
                this.deviceSession.close();
              } finally {
                this.deviceSession.setLocalUserAudioLevelListener(null);
                soundLevelIndicator.resetSoundLevel();
              }
            }

            this.deviceSession = deviceSession;

            if (this.deviceSession != null) {
              this.deviceSession.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW));
              this.deviceSession.setLocalUserAudioLevelListener(audioLevelListener);
              this.deviceSession.start(MediaDirection.SENDONLY);

              try {
                DataSource dataSource = this.deviceSession.getOutputDataSource();

                dataSource.connect();

                PushBufferStream[] streams = ((PushBufferDataSource) dataSource).getStreams();

                for (PushBufferStream stream : streams) stream.setTransferHandler(transferHandler);

                dataSource.start();
              } catch (Throwable t) {
                if (t instanceof ThreadDeath) throw (ThreadDeath) t;
                else setDeviceSession(null);
              }
            }
          }
        };

    if (comboBox != null) comboBox.addActionListener(captureComboActionListener);

    soundLevelIndicator.addHierarchyListener(
        new HierarchyListener() {
          public void hierarchyChanged(HierarchyEvent event) {
            if ((event.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
              SwingUtilities.invokeLater(
                  new Runnable() {
                    public void run() {
                      captureComboActionListener.actionPerformed(null);
                    }
                  });
            }
          }
        });
  }
  /**
   * Creates the UI controls which are to control the details of a specific <tt>AudioSystem</tt>.
   *
   * @param audioSystem the <tt>AudioSystem</tt> for which the UI controls to control its details
   *     are to be created
   * @param container the <tt>JComponent</tt> into which the UI controls which are to control the
   *     details of the specified <tt>audioSystem</tt> are to be added
   */
  public static void createAudioSystemControls(AudioSystem audioSystem, JComponent container) {
    GridBagConstraints constraints = new GridBagConstraints();

    constraints.anchor = GridBagConstraints.NORTHWEST;
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.weighty = 0;

    int audioSystemFeatures = audioSystem.getFeatures();
    boolean featureNotifyAndPlaybackDevices =
        ((audioSystemFeatures & AudioSystem.FEATURE_NOTIFY_AND_PLAYBACK_DEVICES) != 0);

    constraints.gridx = 0;
    constraints.insets = new Insets(3, 0, 3, 3);
    constraints.weightx = 0;

    constraints.gridy = 0;
    container.add(
        new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)), constraints);
    if (featureNotifyAndPlaybackDevices) {
      constraints.gridy = 2;
      container.add(
          new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)), constraints);
      constraints.gridy = 3;
      container.add(
          new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)), constraints);
    }

    constraints.gridx = 1;
    constraints.insets = new Insets(3, 3, 3, 0);
    constraints.weightx = 1;

    JComboBox captureCombo = null;

    if (featureNotifyAndPlaybackDevices) {
      captureCombo = new JComboBox();
      captureCombo.setEditable(false);
      captureCombo.setModel(
          new DeviceConfigurationComboBoxModel(
              captureCombo,
              mediaService.getDeviceConfiguration(),
              DeviceConfigurationComboBoxModel.AUDIO_CAPTURE));
      constraints.gridy = 0;
      container.add(captureCombo, constraints);
    }

    int anchor = constraints.anchor;
    SoundLevelIndicator capturePreview =
        new SoundLevelIndicator(
            SimpleAudioLevelListener.MIN_LEVEL, SimpleAudioLevelListener.MAX_LEVEL);

    constraints.anchor = GridBagConstraints.CENTER;
    constraints.gridy = (captureCombo == null) ? 0 : 1;
    container.add(capturePreview, constraints);
    constraints.anchor = anchor;

    constraints.gridy = GridBagConstraints.RELATIVE;

    if (featureNotifyAndPlaybackDevices) {
      JComboBox playbackCombo = new JComboBox();

      playbackCombo.setEditable(false);
      playbackCombo.setModel(
          new DeviceConfigurationComboBoxModel(
              captureCombo,
              mediaService.getDeviceConfiguration(),
              DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK));
      container.add(playbackCombo, constraints);

      JComboBox notifyCombo = new JComboBox();

      notifyCombo.setEditable(false);
      notifyCombo.setModel(
          new DeviceConfigurationComboBoxModel(
              captureCombo,
              mediaService.getDeviceConfiguration(),
              DeviceConfigurationComboBoxModel.AUDIO_NOTIFY));
      container.add(notifyCombo, constraints);
    }

    if ((AudioSystem.FEATURE_ECHO_CANCELLATION & audioSystemFeatures) != 0) {
      final SIPCommCheckBox echoCancelCheckBox =
          new SIPCommCheckBox(
              NeomediaActivator.getResources().getI18NString("impl.media.configform.ECHOCANCEL"));

      /*
       * First set the selected one, then add the listener in order to
       * avoid saving the value when using the default one and only
       * showing to user without modification.
       */
      echoCancelCheckBox.setSelected(mediaService.getDeviceConfiguration().isEchoCancel());
      echoCancelCheckBox.addItemListener(
          new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
              mediaService.getDeviceConfiguration().setEchoCancel(echoCancelCheckBox.isSelected());
            }
          });
      container.add(echoCancelCheckBox, constraints);
    }

    if ((AudioSystem.FEATURE_DENOISE & audioSystemFeatures) != 0) {
      final SIPCommCheckBox denoiseCheckBox =
          new SIPCommCheckBox(
              NeomediaActivator.getResources().getI18NString("impl.media.configform.DENOISE"));

      /*
       * First set the selected one, then add the listener in order to
       * avoid saving the value when using the default one and only
       * showing to user without modification.
       */
      denoiseCheckBox.setSelected(mediaService.getDeviceConfiguration().isDenoise());
      denoiseCheckBox.addItemListener(
          new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
              mediaService.getDeviceConfiguration().setDenoise(denoiseCheckBox.isSelected());
            }
          });
      container.add(denoiseCheckBox, constraints);
    }

    createAudioPreview(audioSystem, captureCombo, capturePreview);
  }

  /**
   * Creates basic controls for a type (AUDIO or VIDEO).
   *
   * @param type the type.
   * @return the build Component.
   */
  public static Component createBasicControls(final int type) {
    final JComboBox deviceComboBox = new JComboBox();

    deviceComboBox.setEditable(false);
    deviceComboBox.setModel(
        new DeviceConfigurationComboBoxModel(
            deviceComboBox, mediaService.getDeviceConfiguration(), type));

    JLabel deviceLabel = new JLabel(getLabelText(type));

    deviceLabel.setDisplayedMnemonic(getDisplayedMnemonic(type));
    deviceLabel.setLabelFor(deviceComboBox);

    final Container devicePanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));

    devicePanel.setMaximumSize(new Dimension(WIDTH, 25));
    devicePanel.add(deviceLabel);
    devicePanel.add(deviceComboBox);

    final JPanel deviceAndPreviewPanel = new TransparentPanel(new BorderLayout());
    int preferredDeviceAndPreviewPanelHeight;

    switch (type) {
      case DeviceConfigurationComboBoxModel.AUDIO:
        preferredDeviceAndPreviewPanelHeight = 225;
        break;
      case DeviceConfigurationComboBoxModel.VIDEO:
        preferredDeviceAndPreviewPanelHeight = 305;
        break;
      default:
        preferredDeviceAndPreviewPanelHeight = 0;
        break;
    }
    if (preferredDeviceAndPreviewPanelHeight > 0)
      deviceAndPreviewPanel.setPreferredSize(
          new Dimension(WIDTH, preferredDeviceAndPreviewPanelHeight));
    deviceAndPreviewPanel.add(devicePanel, BorderLayout.NORTH);

    final ActionListener deviceComboBoxActionListener =
        new ActionListener() {
          public void actionPerformed(ActionEvent event) {
            boolean revalidateAndRepaint = false;

            for (int i = deviceAndPreviewPanel.getComponentCount() - 1; i >= 0; i--) {
              Component c = deviceAndPreviewPanel.getComponent(i);

              if (c != devicePanel) {
                deviceAndPreviewPanel.remove(i);
                revalidateAndRepaint = true;
              }
            }

            Component preview = null;

            if ((deviceComboBox.getSelectedItem() != null) && deviceComboBox.isShowing()) {
              preview =
                  createPreview(type, deviceComboBox, deviceAndPreviewPanel.getPreferredSize());
            }

            if (preview != null) {
              deviceAndPreviewPanel.add(preview, BorderLayout.CENTER);
              revalidateAndRepaint = true;
            }

            if (revalidateAndRepaint) {
              deviceAndPreviewPanel.revalidate();
              deviceAndPreviewPanel.repaint();
            }
          }
        };

    deviceComboBox.addActionListener(deviceComboBoxActionListener);
    /*
     * We have to initialize the controls to reflect the configuration
     * at the time of creating this instance. Additionally, because the
     * video preview will stop when it and its associated controls
     * become unnecessary, we have to restart it when the mentioned
     * controls become necessary again. We'll address the two goals
     * described by pretending there's a selection in the video combo
     * box when the combo box in question becomes displayable.
     */
    deviceComboBox.addHierarchyListener(
        new HierarchyListener() {
          public void hierarchyChanged(HierarchyEvent event) {
            if ((event.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
              SwingUtilities.invokeLater(
                  new Runnable() {
                    public void run() {
                      deviceComboBoxActionListener.actionPerformed(null);
                    }
                  });
            }
          }
        });

    return deviceAndPreviewPanel;
  }

  /**
   * Creates all the controls (including encoding) for a type(AUDIO or VIDEO)
   *
   * @param type the type.
   * @return the build Component.
   */
  private static Component createControls(int type) {
    ConfigurationService cfg = NeomediaActivator.getConfigurationService();
    SIPCommTabbedPane container = new SIPCommTabbedPane();
    ResourceManagementService res = NeomediaActivator.getResources();

    if ((cfg == null) || !cfg.getBoolean(DEVICES_DISABLED_PROP, false)) {
      container.insertTab(
          res.getI18NString("impl.media.configform.DEVICES"),
          null,
          createBasicControls(type),
          null,
          0);
    }
    if ((cfg == null) || !cfg.getBoolean(ENCODINGS_DISABLED_PROP, false)) {
      container.insertTab(
          res.getI18NString("impl.media.configform.ENCODINGS"),
          null,
          new PriorityTable(
              new EncodingConfigurationTableModel(mediaService.getEncodingConfiguration(), type),
              100),
          null,
          1);
    }
    if ((type == DeviceConfigurationComboBoxModel.VIDEO)
        && ((cfg == null) || !cfg.getBoolean(VIDEO_MORE_SETTINGS_DISABLED_PROP, false))) {
      container.insertTab(
          res.getI18NString("impl.media.configform.VIDEO_MORE_SETTINGS"),
          null,
          createVideoAdvancedSettings(),
          null,
          2);
    }
    return container;
  }

  /**
   * Creates preview for the (video) device in the video container.
   *
   * @param device the device
   * @param videoContainer the video container
   * @throws IOException a problem accessing the device
   * @throws MediaException a problem getting preview
   */
  private static void createVideoPreview(CaptureDeviceInfo device, JComponent videoContainer)
      throws IOException, MediaException {
    videoContainer.removeAll();

    videoContainer.revalidate();
    videoContainer.repaint();

    if (device == null) return;

    for (MediaDevice mediaDevice : mediaService.getDevices(MediaType.VIDEO, MediaUseCase.ANY)) {
      if (((MediaDeviceImpl) mediaDevice).getCaptureDeviceInfo().equals(device)) {
        Dimension videoContainerSize = videoContainer.getPreferredSize();
        Component preview =
            (Component)
                mediaService.getVideoPreviewComponent(
                    mediaDevice, videoContainerSize.width, videoContainerSize.height);

        if (preview != null) videoContainer.add(preview);
        break;
      }
    }
  }

  /**
   * Create preview component.
   *
   * @param type type
   * @param comboBox the options.
   * @param prefSize the preferred size
   * @return the component.
   */
  private static Component createPreview(int type, final JComboBox comboBox, Dimension prefSize) {
    JComponent preview = null;

    if (type == DeviceConfigurationComboBoxModel.AUDIO) {
      Object selectedItem = comboBox.getSelectedItem();

      if (selectedItem instanceof AudioSystem) {
        AudioSystem audioSystem = (AudioSystem) selectedItem;

        if (!NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(audioSystem.getLocatorProtocol())) {
          preview = new TransparentPanel(new GridBagLayout());
          createAudioSystemControls(audioSystem, preview);
        }
      }
    } else if (type == DeviceConfigurationComboBoxModel.VIDEO) {
      JLabel noPreview =
          new JLabel(
              NeomediaActivator.getResources().getI18NString("impl.media.configform.NO_PREVIEW"));

      noPreview.setHorizontalAlignment(SwingConstants.CENTER);
      noPreview.setVerticalAlignment(SwingConstants.CENTER);

      preview = createVideoContainer(noPreview);
      preview.setPreferredSize(prefSize);

      Object selectedItem = comboBox.getSelectedItem();
      CaptureDeviceInfo device = null;
      if (selectedItem instanceof DeviceConfigurationComboBoxModel.CaptureDevice)
        device = ((DeviceConfigurationComboBoxModel.CaptureDevice) selectedItem).info;

      Exception exception;
      try {
        createVideoPreview(device, preview);
        exception = null;
      } catch (IOException ex) {
        exception = ex;
      } catch (MediaException ex) {
        exception = ex;
      }
      if (exception != null) {
        logger.error("Failed to create preview for device " + device, exception);
        device = null;
      }
    }

    return preview;
  }

  /**
   * Creates the video container.
   *
   * @param noVideoComponent the container component.
   * @return the video container.
   */
  private static JComponent createVideoContainer(Component noVideoComponent) {
    return new VideoContainer(noVideoComponent, false);
  }

  /**
   * The mnemonic for a type.
   *
   * @param type audio or video type.
   * @return the mnemonic.
   */
  private static char getDisplayedMnemonic(int type) {
    switch (type) {
      case DeviceConfigurationComboBoxModel.AUDIO:
        return NeomediaActivator.getResources().getI18nMnemonic("impl.media.configform.AUDIO");
      case DeviceConfigurationComboBoxModel.VIDEO:
        return NeomediaActivator.getResources().getI18nMnemonic("impl.media.configform.VIDEO");
      default:
        throw new IllegalArgumentException("type");
    }
  }

  /**
   * A label for a type.
   *
   * @param type the type.
   * @return the label.
   */
  private static String getLabelText(int type) {
    switch (type) {
      case DeviceConfigurationComboBoxModel.AUDIO:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO");
      case DeviceConfigurationComboBoxModel.AUDIO_CAPTURE:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_IN");
      case DeviceConfigurationComboBoxModel.AUDIO_NOTIFY:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_NOTIFY");
      case DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_OUT");
      case DeviceConfigurationComboBoxModel.VIDEO:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.VIDEO");
      default:
        throw new IllegalArgumentException("type");
    }
  }

  /**
   * Creates the video advanced settings.
   *
   * @return video advanced settings panel.
   */
  private static Component createVideoAdvancedSettings() {
    ResourceManagementService resources = NeomediaActivator.getResources();

    final DeviceConfiguration deviceConfig = mediaService.getDeviceConfiguration();

    TransparentPanel centerPanel = new TransparentPanel(new GridBagLayout());
    centerPanel.setMaximumSize(new Dimension(WIDTH, 150));

    JButton resetDefaultsButton =
        new JButton(resources.getI18NString("impl.media.configform.VIDEO_RESET"));
    JPanel resetButtonPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));
    resetButtonPanel.add(resetDefaultsButton);

    final JPanel centerAdvancedPanel = new TransparentPanel(new BorderLayout());
    centerAdvancedPanel.add(centerPanel, BorderLayout.NORTH);
    centerAdvancedPanel.add(resetButtonPanel, BorderLayout.SOUTH);

    GridBagConstraints constraints = new GridBagConstraints();
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.anchor = GridBagConstraints.NORTHWEST;
    constraints.insets = new Insets(5, 5, 0, 0);
    constraints.gridx = 0;
    constraints.weightx = 0;
    constraints.weighty = 0;
    constraints.gridy = 0;

    centerPanel.add(
        new JLabel(resources.getI18NString("impl.media.configform.VIDEO_RESOLUTION")), constraints);
    constraints.gridy = 1;
    constraints.insets = new Insets(0, 0, 0, 0);
    final JCheckBox frameRateCheck =
        new SIPCommCheckBox(resources.getI18NString("impl.media.configform.VIDEO_FRAME_RATE"));
    centerPanel.add(frameRateCheck, constraints);
    constraints.gridy = 2;
    constraints.insets = new Insets(5, 5, 0, 0);
    centerPanel.add(
        new JLabel(resources.getI18NString("impl.media.configform.VIDEO_PACKETS_POLICY")),
        constraints);

    constraints.weightx = 1;
    constraints.gridx = 1;
    constraints.gridy = 0;
    constraints.insets = new Insets(5, 0, 0, 5);
    Object[] resolutionValues = new Object[DeviceConfiguration.SUPPORTED_RESOLUTIONS.length + 1];
    System.arraycopy(
        DeviceConfiguration.SUPPORTED_RESOLUTIONS,
        0,
        resolutionValues,
        1,
        DeviceConfiguration.SUPPORTED_RESOLUTIONS.length);
    final JComboBox sizeCombo = new JComboBox(resolutionValues);
    sizeCombo.setRenderer(new ResolutionCellRenderer());
    sizeCombo.setEditable(false);
    centerPanel.add(sizeCombo, constraints);

    // default value is 20
    final JSpinner frameRate = new JSpinner(new SpinnerNumberModel(20, 5, 30, 1));
    frameRate.addChangeListener(
        new ChangeListener() {
          public void stateChanged(ChangeEvent e) {
            deviceConfig.setFrameRate(
                ((SpinnerNumberModel) frameRate.getModel()).getNumber().intValue());
          }
        });
    constraints.gridy = 1;
    constraints.insets = new Insets(0, 0, 0, 5);
    centerPanel.add(frameRate, constraints);

    frameRateCheck.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (frameRateCheck.isSelected()) {
              deviceConfig.setFrameRate(
                  ((SpinnerNumberModel) frameRate.getModel()).getNumber().intValue());
            } else // unlimited framerate
            deviceConfig.setFrameRate(-1);

            frameRate.setEnabled(frameRateCheck.isSelected());
          }
        });

    final JSpinner videoMaxBandwidth =
        new JSpinner(
            new SpinnerNumberModel(deviceConfig.getVideoMaxBandwidth(), 1, Integer.MAX_VALUE, 1));
    videoMaxBandwidth.addChangeListener(
        new ChangeListener() {
          public void stateChanged(ChangeEvent e) {
            deviceConfig.setVideoMaxBandwidth(
                ((SpinnerNumberModel) videoMaxBandwidth.getModel()).getNumber().intValue());
          }
        });
    constraints.gridx = 1;
    constraints.gridy = 2;
    constraints.insets = new Insets(0, 0, 5, 5);
    centerPanel.add(videoMaxBandwidth, constraints);

    resetDefaultsButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            // reset to defaults
            sizeCombo.setSelectedIndex(0);
            frameRateCheck.setSelected(false);
            frameRate.setEnabled(false);
            frameRate.setValue(20);
            // unlimited framerate
            deviceConfig.setFrameRate(-1);
            videoMaxBandwidth.setValue(DeviceConfiguration.DEFAULT_VIDEO_MAX_BANDWIDTH);
          }
        });

    // load selected value or auto
    Dimension videoSize = deviceConfig.getVideoSize();

    if ((videoSize.getHeight() != DeviceConfiguration.DEFAULT_VIDEO_HEIGHT)
        && (videoSize.getWidth() != DeviceConfiguration.DEFAULT_VIDEO_WIDTH))
      sizeCombo.setSelectedItem(deviceConfig.getVideoSize());
    else sizeCombo.setSelectedIndex(0);
    sizeCombo.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            Dimension selectedVideoSize = (Dimension) sizeCombo.getSelectedItem();

            if (selectedVideoSize == null) {
              // the auto value, default one
              selectedVideoSize =
                  new Dimension(
                      DeviceConfiguration.DEFAULT_VIDEO_WIDTH,
                      DeviceConfiguration.DEFAULT_VIDEO_HEIGHT);
            }
            deviceConfig.setVideoSize(selectedVideoSize);
          }
        });

    frameRateCheck.setSelected(
        deviceConfig.getFrameRate() != DeviceConfiguration.DEFAULT_VIDEO_FRAMERATE);
    frameRate.setEnabled(frameRateCheck.isSelected());

    if (frameRate.isEnabled()) frameRate.setValue(deviceConfig.getFrameRate());

    return centerAdvancedPanel;
  }

  /** Renders the available resolutions in the combo box. */
  private static class ResolutionCellRenderer extends DefaultListCellRenderer {
    /**
     * The serialization version number of the <tt>ResolutionCellRenderer</tt> class. Defined to the
     * value of <tt>0</tt> because the <tt>ResolutionCellRenderer</tt> instances do not have state
     * of their own.
     */
    private static final long serialVersionUID = 0L;

    /**
     * Sets readable text describing the resolution if the selected value is null we return the
     * string "Auto".
     *
     * @param list
     * @param value
     * @param index
     * @param isSelected
     * @param cellHasFocus
     * @return Component
     */
    @Override
    public Component getListCellRendererComponent(
        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      // call super to set backgrounds and fonts
      super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

      // now just change the text
      if (value == null) setText("Auto");
      else if (value instanceof Dimension) {
        Dimension d = (Dimension) value;

        setText(((int) d.getWidth()) + "x" + ((int) d.getHeight()));
      }
      return this;
    }
  }
}