예제 #1
0
  /**
   * 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);
    }
  }
예제 #2
0
  /**
   * 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;
    }
  }
예제 #3
0
  /**
   * 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);
    }
  }
예제 #4
0
  /**
   * 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();
  }
예제 #5
0
  /**
   * 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);
  }
예제 #6
0
/**
 * 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;
  }
}