diff options
Diffstat (limited to 'voip/java')
-rw-r--r-- | voip/java/android/net/sip/SdpSessionDescription.java | 428 | ||||
-rw-r--r-- | voip/java/android/net/sip/SessionDescription.aidl | 19 | ||||
-rw-r--r-- | voip/java/android/net/sip/SessionDescription.java | 83 | ||||
-rw-r--r-- | voip/java/android/net/sip/SipAudioCall.java | 957 | ||||
-rw-r--r-- | voip/java/android/net/sip/SipAudioCallImpl.java | 738 | ||||
-rw-r--r-- | voip/java/android/net/sip/SipManager.java | 169 | ||||
-rw-r--r-- | voip/java/android/net/sip/SipProfile.java | 5 | ||||
-rw-r--r-- | voip/java/android/net/sip/SipSession.java | 531 | ||||
-rw-r--r-- | voip/java/android/net/sip/SipSessionState.java | 94 |
9 files changed, 1461 insertions, 1563 deletions
diff --git a/voip/java/android/net/sip/SdpSessionDescription.java b/voip/java/android/net/sip/SdpSessionDescription.java deleted file mode 100644 index f6ae837..0000000 --- a/voip/java/android/net/sip/SdpSessionDescription.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.sip; - -import gov.nist.javax.sdp.SessionDescriptionImpl; -import gov.nist.javax.sdp.fields.AttributeField; -import gov.nist.javax.sdp.fields.ConnectionField; -import gov.nist.javax.sdp.fields.MediaField; -import gov.nist.javax.sdp.fields.OriginField; -import gov.nist.javax.sdp.fields.ProtoVersionField; -import gov.nist.javax.sdp.fields.SessionNameField; -import gov.nist.javax.sdp.fields.TimeField; -import gov.nist.javax.sdp.parser.SDPAnnounceParser; - -import android.util.Log; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Vector; -import javax.sdp.Connection; -import javax.sdp.MediaDescription; -import javax.sdp.SdpException; - -/** - * A session description that follows SDP (Session Description Protocol). - * Refer to <a href="http://tools.ietf.org/html/rfc4566">RFC 4566</a>. - * @hide - */ -public class SdpSessionDescription extends SessionDescription { - private static final String TAG = "SDP"; - private static final String AUDIO = "audio"; - private static final String RTPMAP = "rtpmap"; - private static final String PTIME = "ptime"; - private static final String SENDONLY = "sendonly"; - private static final String RECVONLY = "recvonly"; - private static final String INACTIVE = "inactive"; - - private SessionDescriptionImpl mSessionDescription; - - /** - * The audio codec information parsed from "rtpmap". - */ - public static class AudioCodec { - public final int payloadType; - public final String name; - public final int sampleRate; - public final int sampleCount; - - public AudioCodec(int payloadType, String name, int sampleRate, - int sampleCount) { - this.payloadType = payloadType; - this.name = name; - this.sampleRate = sampleRate; - this.sampleCount = sampleCount; - } - } - - /** - * The builder class used to create an {@link SdpSessionDescription} object. - */ - public static class Builder { - private SdpSessionDescription mSdp = new SdpSessionDescription(); - private SessionDescriptionImpl mSessionDescription; - - public Builder(String sessionName) throws SdpException { - mSessionDescription = new SessionDescriptionImpl(); - mSdp.mSessionDescription = mSessionDescription; - try { - ProtoVersionField proto = new ProtoVersionField(); - proto.setVersion(0); - mSessionDescription.addField(proto); - - TimeField time = new TimeField(); - time.setZero(); - mSessionDescription.addField(time); - - SessionNameField session = new SessionNameField(); - session.setValue(sessionName); - mSessionDescription.addField(session); - } catch (Exception e) { - throwSdpException(e); - } - } - - public Builder setConnectionInfo(String networkType, String addressType, - String addr) throws SdpException { - try { - ConnectionField connection = new ConnectionField(); - connection.setNetworkType(networkType); - connection.setAddressType(addressType); - connection.setAddress(addr); - mSessionDescription.addField(connection); - } catch (Exception e) { - throwSdpException(e); - } - return this; - } - - public Builder setOrigin(SipProfile user, long sessionId, - long sessionVersion, String networkType, String addressType, - String address) throws SdpException { - try { - OriginField origin = new OriginField(); - origin.setUsername(user.getUserName()); - origin.setSessionId(sessionId); - origin.setSessionVersion(sessionVersion); - origin.setAddressType(addressType); - origin.setNetworkType(networkType); - origin.setAddress(address); - mSessionDescription.addField(origin); - } catch (Exception e) { - throwSdpException(e); - } - return this; - } - - public Builder addMedia(String media, int port, int numPorts, - String transport, Integer... types) throws SdpException { - MediaField field = new MediaField(); - Vector<Integer> typeVector = new Vector<Integer>(); - Collections.addAll(typeVector, types); - try { - field.setMediaType(media); - field.setMediaPort(port); - field.setPortCount(numPorts); - field.setProtocol(transport); - field.setMediaFormats(typeVector); - mSessionDescription.addField(field); - } catch (Exception e) { - throwSdpException(e); - } - return this; - } - - public Builder addMediaAttribute(String type, String name, String value) - throws SdpException { - try { - MediaDescription md = mSdp.getMediaDescription(type); - if (md == null) { - throw new SdpException("Should add media first!"); - } - AttributeField attribute = new AttributeField(); - attribute.setName(name); - attribute.setValueAllowNull(value); - mSessionDescription.addField(attribute); - } catch (Exception e) { - throwSdpException(e); - } - return this; - } - - public Builder addSessionAttribute(String name, String value) - throws SdpException { - try { - AttributeField attribute = new AttributeField(); - attribute.setName(name); - attribute.setValueAllowNull(value); - mSessionDescription.addField(attribute); - } catch (Exception e) { - throwSdpException(e); - } - return this; - } - - private void throwSdpException(Exception e) throws SdpException { - if (e instanceof SdpException) { - throw (SdpException) e; - } else { - throw new SdpException(e.toString(), e); - } - } - - public String build() { - return mSdp.toString(); - } - } - - private SdpSessionDescription() { - } - - /** - * Constructor. - * - * @param sdpString an SDP session description to parse - */ - public SdpSessionDescription(String sdpString) throws SdpException { - try { - mSessionDescription = new SDPAnnounceParser(sdpString).parse(); - } catch (ParseException e) { - throw new SdpException(e.toString(), e); - } - verify(); - } - - /** - * Constructor. - * - * @param content a raw SDP session description to parse - */ - public SdpSessionDescription(byte[] content) throws SdpException { - this(new String(content)); - } - - private void verify() throws SdpException { - // make sure the syntax is correct over the fields we're interested in - Vector<MediaDescription> descriptions = (Vector<MediaDescription>) - mSessionDescription.getMediaDescriptions(false); - for (MediaDescription md : descriptions) { - md.getMedia().getMediaPort(); - Connection connection = md.getConnection(); - if (connection != null) connection.getAddress(); - md.getMedia().getFormats(); - } - Connection connection = mSessionDescription.getConnection(); - if (connection != null) connection.getAddress(); - } - - /** - * Gets the connection address of the media. - * - * @param type the media type; e.g., "AUDIO" - * @return the media connection address of the peer - */ - public String getPeerMediaAddress(String type) { - try { - MediaDescription md = getMediaDescription(type); - Connection connection = md.getConnection(); - if (connection == null) { - connection = mSessionDescription.getConnection(); - } - return ((connection == null) ? null : connection.getAddress()); - } catch (SdpException e) { - // should not occur - return null; - } - } - - /** - * Gets the connection port number of the media. - * - * @param type the media type; e.g., "AUDIO" - * @return the media connection port number of the peer - */ - public int getPeerMediaPort(String type) { - try { - MediaDescription md = getMediaDescription(type); - return md.getMedia().getMediaPort(); - } catch (SdpException e) { - // should not occur - return -1; - } - } - - private boolean containsAttribute(String type, String name) { - if (name == null) return false; - MediaDescription md = getMediaDescription(type); - Vector<AttributeField> v = (Vector<AttributeField>) - md.getAttributeFields(); - for (AttributeField field : v) { - if (name.equals(field.getAttribute().getName())) return true; - } - return false; - } - - /** - * Checks if the media is "sendonly". - * - * @param type the media type; e.g., "AUDIO" - * @return true if the media is "sendonly" - */ - public boolean isSendOnly(String type) { - boolean answer = containsAttribute(type, SENDONLY); - Log.d(TAG, " sendonly? " + answer); - return answer; - } - - /** - * Checks if the media is "recvonly". - * - * @param type the media type; e.g., "AUDIO" - * @return true if the media is "recvonly" - */ - public boolean isReceiveOnly(String type) { - boolean answer = containsAttribute(type, RECVONLY); - Log.d(TAG, " recvonly? " + answer); - return answer; - } - - /** - * Checks if the media is in sending; i.e., not "recvonly" and not - * "inactive". - * - * @param type the media type; e.g., "AUDIO" - * @return true if the media is sending - */ - public boolean isSending(String type) { - boolean answer = !containsAttribute(type, RECVONLY) - && !containsAttribute(type, INACTIVE); - - Log.d(TAG, " sending? " + answer); - return answer; - } - - /** - * Checks if the media is in receiving; i.e., not "sendonly" and not - * "inactive". - * - * @param type the media type; e.g., "AUDIO" - * @return true if the media is receiving - */ - public boolean isReceiving(String type) { - boolean answer = !containsAttribute(type, SENDONLY) - && !containsAttribute(type, INACTIVE); - Log.d(TAG, " receiving? " + answer); - return answer; - } - - private AudioCodec parseAudioCodec(String rtpmap, int ptime) { - String[] ss = rtpmap.split(" "); - int payloadType = Integer.parseInt(ss[0]); - - ss = ss[1].split("/"); - String name = ss[0]; - int sampleRate = Integer.parseInt(ss[1]); - int channelCount = 1; - if (ss.length > 2) channelCount = Integer.parseInt(ss[2]); - int sampleCount = sampleRate / (1000 / ptime) * channelCount; - return new AudioCodec(payloadType, name, sampleRate, sampleCount); - } - - /** - * Gets the list of audio codecs in this session description. - * - * @return the list of audio codecs in this session description - */ - public List<AudioCodec> getAudioCodecs() { - MediaDescription md = getMediaDescription(AUDIO); - if (md == null) return new ArrayList<AudioCodec>(); - - // FIXME: what happens if ptime is missing - int ptime = 20; - try { - String value = md.getAttribute(PTIME); - if (value != null) ptime = Integer.parseInt(value); - } catch (Throwable t) { - Log.w(TAG, "getCodecs(): ignored: " + t); - } - - List<AudioCodec> codecs = new ArrayList<AudioCodec>(); - Vector<AttributeField> v = (Vector<AttributeField>) - md.getAttributeFields(); - for (AttributeField field : v) { - try { - if (RTPMAP.equals(field.getName())) { - AudioCodec codec = parseAudioCodec(field.getValue(), ptime); - if (codec != null) codecs.add(codec); - } - } catch (Throwable t) { - Log.w(TAG, "getCodecs(): ignored: " + t); - } - } - return codecs; - } - - /** - * Gets the media description of the specified type. - * - * @param type the media type; e.g., "AUDIO" - * @return the media description of the specified type - */ - public MediaDescription getMediaDescription(String type) { - MediaDescription[] all = getMediaDescriptions(); - if ((all == null) || (all.length == 0)) return null; - for (MediaDescription md : all) { - String t = md.getMedia().getMedia(); - if (t.equalsIgnoreCase(type)) return md; - } - return null; - } - - /** - * Gets all the media descriptions in this session description. - * - * @return all the media descriptions in this session description - */ - public MediaDescription[] getMediaDescriptions() { - try { - Vector<MediaDescription> descriptions = (Vector<MediaDescription>) - mSessionDescription.getMediaDescriptions(false); - MediaDescription[] all = new MediaDescription[descriptions.size()]; - return descriptions.toArray(all); - } catch (SdpException e) { - Log.e(TAG, "getMediaDescriptions", e); - } - return null; - } - - @Override - public String getType() { - return "sdp"; - } - - @Override - public byte[] getContent() { - return mSessionDescription.toString().getBytes(); - } - - @Override - public String toString() { - return mSessionDescription.toString(); - } -} diff --git a/voip/java/android/net/sip/SessionDescription.aidl b/voip/java/android/net/sip/SessionDescription.aidl deleted file mode 100644 index a120d16..0000000 --- a/voip/java/android/net/sip/SessionDescription.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2010, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.sip; - -parcelable SessionDescription; diff --git a/voip/java/android/net/sip/SessionDescription.java b/voip/java/android/net/sip/SessionDescription.java deleted file mode 100644 index d476f0b..0000000 --- a/voip/java/android/net/sip/SessionDescription.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.sip; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Abstract class of a session description. - * @hide - */ -public abstract class SessionDescription implements Parcelable { - /** @hide */ - public static final Parcelable.Creator<SessionDescription> CREATOR = - new Parcelable.Creator<SessionDescription>() { - public SessionDescription createFromParcel(Parcel in) { - return new SessionDescriptionImpl(in); - } - - public SessionDescription[] newArray(int size) { - return new SessionDescriptionImpl[size]; - } - }; - - /** - * Gets the type of the session description; e.g., "SDP". - * - * @return the session description type - */ - public abstract String getType(); - - /** - * Gets the raw content of the session description. - * - * @return the content of the session description - */ - public abstract byte[] getContent(); - - /** @hide */ - public void writeToParcel(Parcel out, int flags) { - out.writeString(getType()); - out.writeByteArray(getContent()); - } - - /** @hide */ - public int describeContents() { - return 0; - } - - private static class SessionDescriptionImpl extends SessionDescription { - private String mType; - private byte[] mContent; - - SessionDescriptionImpl(Parcel in) { - mType = in.readString(); - mContent = in.createByteArray(); - } - - @Override - public String getType() { - return mType; - } - - @Override - public byte[] getContent() { - return mContent; - } - } -} diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java index 0069fe0..2135fcb 100644 --- a/voip/java/android/net/sip/SipAudioCall.java +++ b/voip/java/android/net/sip/SipAudioCall.java @@ -16,120 +16,184 @@ package android.net.sip; +import android.content.Context; +import android.media.AudioManager; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.media.ToneGenerator; +import android.net.Uri; +import android.net.rtp.AudioCodec; import android.net.rtp.AudioGroup; import android.net.rtp.AudioStream; +import android.net.rtp.RtpStream; +import android.net.sip.SimpleSessionDescription.Media; +import android.net.wifi.WifiManager; import android.os.Message; +import android.os.RemoteException; +import android.os.Vibrator; +import android.provider.Settings; +import android.util.Log; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** - * Interface for making audio calls over SIP. - * @hide + * Class that handles an audio call over SIP. */ -public interface SipAudioCall { +/** @hide */ +public class SipAudioCall extends SipSessionAdapter { + private static final String TAG = SipAudioCall.class.getSimpleName(); + private static final boolean RELEASE_SOCKET = true; + private static final boolean DONT_RELEASE_SOCKET = false; + private static final int SESSION_TIMEOUT = 5; // in seconds + /** Listener class for all event callbacks. */ - public interface Listener { + public static class Listener { /** * Called when the call object is ready to make another call. + * The default implementation calls {@link #onChange}. * * @param call the call object that is ready to make another call */ - void onReadyToCall(SipAudioCall call); + public void onReadyToCall(SipAudioCall call) { + onChanged(call); + } /** * Called when a request is sent out to initiate a new call. + * The default implementation calls {@link #onChange}. * * @param call the call object that carries out the audio call */ - void onCalling(SipAudioCall call); + public void onCalling(SipAudioCall call) { + onChanged(call); + } /** * Called when a new call comes in. + * The default implementation calls {@link #onChange}. * * @param call the call object that carries out the audio call * @param caller the SIP profile of the caller */ - void onRinging(SipAudioCall call, SipProfile caller); + public void onRinging(SipAudioCall call, SipProfile caller) { + onChanged(call); + } /** - * Called when a RINGING response is received for the INVITE request sent + * Called when a RINGING response is received for the INVITE request + * sent. The default implementation calls {@link #onChange}. * * @param call the call object that carries out the audio call */ - void onRingingBack(SipAudioCall call); + public void onRingingBack(SipAudioCall call) { + onChanged(call); + } /** * Called when the session is established. + * The default implementation calls {@link #onChange}. * * @param call the call object that carries out the audio call */ - void onCallEstablished(SipAudioCall call); + public void onCallEstablished(SipAudioCall call) { + onChanged(call); + } /** * Called when the session is terminated. + * The default implementation calls {@link #onChange}. * * @param call the call object that carries out the audio call */ - void onCallEnded(SipAudioCall call); + public void onCallEnded(SipAudioCall call) { + onChanged(call); + } /** * Called when the peer is busy during session initialization. + * The default implementation calls {@link #onChange}. * * @param call the call object that carries out the audio call */ - void onCallBusy(SipAudioCall call); + public void onCallBusy(SipAudioCall call) { + onChanged(call); + } /** * Called when the call is on hold. + * The default implementation calls {@link #onChange}. * * @param call the call object that carries out the audio call */ - void onCallHeld(SipAudioCall call); + public void onCallHeld(SipAudioCall call) { + onChanged(call); + } /** - * Called when an error occurs. + * Called when an error occurs. The default implementation is no op. * * @param call the call object that carries out the audio call * @param errorCode error code of this error * @param errorMessage error message * @see SipErrorCode */ - void onError(SipAudioCall call, int errorCode, String errorMessage); + public void onError(SipAudioCall call, int errorCode, + String errorMessage) { + // no-op + } + + /** + * Called when an event occurs and the corresponding callback is not + * overridden. The default implementation is no op. Error events are + * not re-directed to this callback and are handled in {@link #onError}. + */ + public void onChanged(SipAudioCall call) { + // no-op + } } + private Context mContext; + private SipProfile mLocalProfile; + private SipAudioCall.Listener mListener; + private SipSession mSipSession; + + private long mSessionId = System.currentTimeMillis(); + private String mPeerSd; + + private AudioStream mAudioStream; + private AudioGroup mAudioGroup; + + private boolean mInCall = false; + private boolean mMuted = false; + private boolean mHold = false; + + private boolean mRingbackToneEnabled = true; + private boolean mRingtoneEnabled = true; + private Ringtone mRingtone; + private ToneGenerator mRingbackTone; + + private SipProfile mPendingCallRequest; + private WifiManager mWm; + private WifiManager.WifiLock mWifiHighPerfLock; + + private int mErrorCode = SipErrorCode.NO_ERROR; + private String mErrorMessage; + /** - * The adapter class for {@link Listener}. The default implementation of - * all callback methods is no-op. + * Creates a call object with the local SIP profile. + * @param context the context for accessing system services such as + * ringtone, audio, WIFI etc */ - public class Adapter implements Listener { - protected void onChanged(SipAudioCall call) { - } - public void onReadyToCall(SipAudioCall call) { - onChanged(call); - } - public void onCalling(SipAudioCall call) { - onChanged(call); - } - public void onRinging(SipAudioCall call, SipProfile caller) { - onChanged(call); - } - public void onRingingBack(SipAudioCall call) { - onChanged(call); - } - public void onCallEstablished(SipAudioCall call) { - onChanged(call); - } - public void onCallEnded(SipAudioCall call) { - onChanged(call); - } - public void onCallBusy(SipAudioCall call) { - onChanged(call); - } - public void onCallHeld(SipAudioCall call) { - onChanged(call); - } - public void onError(SipAudioCall call, int errorCode, - String errorMessage) { - onChanged(call); - } + public SipAudioCall(Context context, SipProfile localProfile) { + mContext = context; + mLocalProfile = localProfile; + mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); } /** @@ -139,7 +203,9 @@ public interface SipAudioCall { * @param listener to listen to the audio call events of this object * @see #setListener(Listener, boolean) */ - void setListener(Listener listener); + public void setListener(SipAudioCall.Listener listener) { + setListener(listener, false); + } /** * Sets the listener to listen to the audio call events. A @@ -150,44 +216,355 @@ public interface SipAudioCall { * @param callbackImmediately set to true if the caller wants to be called * back immediately on the current state */ - void setListener(Listener listener, boolean callbackImmediately); + public void setListener(SipAudioCall.Listener listener, + boolean callbackImmediately) { + mListener = listener; + try { + if ((listener == null) || !callbackImmediately) { + // do nothing + } else if (mErrorCode != SipErrorCode.NO_ERROR) { + listener.onError(this, mErrorCode, mErrorMessage); + } else if (mInCall) { + if (mHold) { + listener.onCallHeld(this); + } else { + listener.onCallEstablished(this); + } + } else { + int state = getState(); + switch (state) { + case SipSession.State.READY_TO_CALL: + listener.onReadyToCall(this); + break; + case SipSession.State.INCOMING_CALL: + listener.onRinging(this, getPeerProfile()); + break; + case SipSession.State.OUTGOING_CALL: + listener.onCalling(this); + break; + case SipSession.State.OUTGOING_CALL_RING_BACK: + listener.onRingingBack(this); + break; + } + } + } catch (Throwable t) { + Log.e(TAG, "setListener()", t); + } + } + + /** + * Checks if the call is established. + * + * @return true if the call is established + */ + public synchronized boolean isInCall() { + return mInCall; + } + + /** + * Checks if the call is on hold. + * + * @return true if the call is on hold + */ + public synchronized boolean isOnHold() { + return mHold; + } /** * Closes this object. This object is not usable after being closed. */ - void close(); + public void close() { + close(true); + } + + private synchronized void close(boolean closeRtp) { + if (closeRtp) stopCall(RELEASE_SOCKET); + stopRingbackTone(); + stopRinging(); + + mInCall = false; + mHold = false; + mSessionId = System.currentTimeMillis(); + mErrorCode = SipErrorCode.NO_ERROR; + mErrorMessage = null; + + if (mSipSession != null) { + mSipSession.setListener(null); + mSipSession = null; + } + } /** - * Initiates an audio call to the specified profile. The attempt will be - * timed out if the call is not established within {@code timeout} seconds - * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} - * will be called. + * Gets the local SIP profile. * - * @param callee the SIP profile to make the call to - * @param sipManager the {@link SipManager} object to help make call with - * @param timeout the timeout value in seconds - * @see Listener.onError + * @return the local SIP profile */ - void makeCall(SipProfile callee, SipManager sipManager, int timeout) - throws SipException; + public synchronized SipProfile getLocalProfile() { + return mLocalProfile; + } /** - * Starts the audio for the established call. This method should be called - * after {@link Listener#onCallEstablished} is called. + * Gets the peer's SIP profile. + * + * @return the peer's SIP profile */ - void startAudio(); + public synchronized SipProfile getPeerProfile() { + return (mSipSession == null) ? null : mSipSession.getPeerProfile(); + } + + /** + * Gets the state of the {@link SipSession} that carries this call. + * The value returned must be one of the states in {@link SipSession.State}. + * + * @return the session state + */ + public synchronized int getState() { + if (mSipSession == null) return SipSession.State.READY_TO_CALL; + return mSipSession.getState(); + } + + + /** + * Gets the {@link SipSession} that carries this call. + * + * @return the session object that carries this call + * @hide + */ + public synchronized SipSession getSipSession() { + return mSipSession; + } + + private SipSession.Listener createListener() { + return new SipSession.Listener() { + @Override + public void onCalling(SipSession session) { + Log.d(TAG, "calling... " + session); + Listener listener = mListener; + if (listener != null) { + try { + listener.onCalling(SipAudioCall.this); + } catch (Throwable t) { + Log.i(TAG, "onCalling(): " + t); + } + } + } + + @Override + public void onRingingBack(SipSession session) { + Log.d(TAG, "sip call ringing back: " + session); + if (!mInCall) startRingbackTone(); + Listener listener = mListener; + if (listener != null) { + try { + listener.onRingingBack(SipAudioCall.this); + } catch (Throwable t) { + Log.i(TAG, "onRingingBack(): " + t); + } + } + } + + @Override + public synchronized void onRinging(SipSession session, + SipProfile peerProfile, String sessionDescription) { + if ((mSipSession == null) || !mInCall + || !session.getCallId().equals(mSipSession.getCallId())) { + // should not happen + session.endCall(); + return; + } + + // session changing request + try { + String answer = createAnswer(sessionDescription).encode(); + mSipSession.answerCall(answer, SESSION_TIMEOUT); + } catch (Throwable e) { + Log.e(TAG, "onRinging()", e); + session.endCall(); + } + } + + @Override + public void onCallEstablished(SipSession session, + String sessionDescription) { + stopRingbackTone(); + stopRinging(); + mPeerSd = sessionDescription; + Log.v(TAG, "onCallEstablished()" + mPeerSd); + + Listener listener = mListener; + if (listener != null) { + try { + if (mHold) { + listener.onCallHeld(SipAudioCall.this); + } else { + listener.onCallEstablished(SipAudioCall.this); + } + } catch (Throwable t) { + Log.i(TAG, "onCallEstablished(): " + t); + } + } + } + + @Override + public void onCallEnded(SipSession session) { + Log.d(TAG, "sip call ended: " + session); + Listener listener = mListener; + if (listener != null) { + try { + listener.onCallEnded(SipAudioCall.this); + } catch (Throwable t) { + Log.i(TAG, "onCallEnded(): " + t); + } + } + close(); + } + + @Override + public void onCallBusy(SipSession session) { + Log.d(TAG, "sip call busy: " + session); + Listener listener = mListener; + if (listener != null) { + try { + listener.onCallBusy(SipAudioCall.this); + } catch (Throwable t) { + Log.i(TAG, "onCallBusy(): " + t); + } + } + close(false); + } + + @Override + public void onCallChangeFailed(SipSession session, int errorCode, + String message) { + Log.d(TAG, "sip call change failed: " + message); + mErrorCode = errorCode; + mErrorMessage = message; + Listener listener = mListener; + if (listener != null) { + try { + listener.onError(SipAudioCall.this, mErrorCode, + message); + } catch (Throwable t) { + Log.i(TAG, "onCallBusy(): " + t); + } + } + } + + @Override + public void onError(SipSession session, int errorCode, + String message) { + SipAudioCall.this.onError(errorCode, message); + } + + @Override + public void onRegistering(SipSession session) { + // irrelevant + } + + @Override + public void onRegistrationTimeout(SipSession session) { + // irrelevant + } + + @Override + public void onRegistrationFailed(SipSession session, int errorCode, + String message) { + // irrelevant + } + + @Override + public void onRegistrationDone(SipSession session, int duration) { + // irrelevant + } + }; + } + + private void onError(int errorCode, String message) { + Log.d(TAG, "sip session error: " + + SipErrorCode.toString(errorCode) + ": " + message); + mErrorCode = errorCode; + mErrorMessage = message; + Listener listener = mListener; + if (listener != null) { + try { + listener.onError(this, errorCode, message); + } catch (Throwable t) { + Log.i(TAG, "onError(): " + t); + } + } + synchronized (this) { + if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST) + || !isInCall()) { + close(true); + } + } + } /** * Attaches an incoming call to this call object. * * @param session the session that receives the incoming call * @param sessionDescription the session description of the incoming call + * @throws SipException if the SIP service fails to attach this object to + * the session + */ + public synchronized void attachCall(SipSession session, + String sessionDescription) throws SipException { + mSipSession = session; + mPeerSd = sessionDescription; + Log.v(TAG, "attachCall()" + mPeerSd); + try { + session.setListener(createListener()); + + if (getState() == SipSession.State.INCOMING_CALL) startRinging(); + } catch (Throwable e) { + Log.e(TAG, "attachCall()", e); + throwSipException(e); + } + } + + /** + * Initiates an audio call to the specified profile. The attempt will be + * timed out if the call is not established within {@code timeout} seconds + * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * will be called. + * + * @param callee the SIP profile to make the call to + * @param sipManager the {@link SipManager} object to help make call with + * @param timeout the timeout value in seconds. Default value (defined by + * SIP protocol) is used if {@code timeout} is zero or negative. + * @see Listener.onError + * @throws SipException if the SIP service fails to create a session for the + * call */ - void attachCall(ISipSession session, String sessionDescription) - throws SipException; + public synchronized void makeCall(SipProfile peerProfile, + SipManager sipManager, int timeout) throws SipException { + SipSession s = mSipSession = sipManager.createSipSession( + mLocalProfile, createListener()); + if (s == null) { + throw new SipException( + "Failed to create SipSession; network available?"); + } + try { + mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); + s.makeCall(peerProfile, createOffer().encode(), timeout); + } catch (IOException e) { + throw new SipException("makeCall()", e); + } + } - /** Ends a call. */ - void endCall() throws SipException; + /** + * Ends a call. + * @throws SipException if the SIP service fails to end the call + */ + public synchronized void endCall() throws SipException { + stopRinging(); + stopCall(RELEASE_SOCKET); + mInCall = false; + + // perform the above local ops first and then network op + if (mSipSession != null) mSipSession.endCall(); + } /** * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is @@ -196,10 +573,19 @@ public interface SipAudioCall { * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * - * @param timeout the timeout value in seconds + * @param timeout the timeout value in seconds. Default value (defined by + * SIP protocol) is used if {@code timeout} is zero or negative. * @see Listener.onError + * @throws SipException if the SIP service fails to hold the call */ - void holdCall(int timeout) throws SipException; + public synchronized void holdCall(int timeout) throws SipException { + if (mHold) return; + mSipSession.changeCall(createHoldOffer().encode(), timeout); + mHold = true; + + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + } /** * Answers a call. The attempt will be timed out if the call is not @@ -207,10 +593,20 @@ public interface SipAudioCall { * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * - * @param timeout the timeout value in seconds + * @param timeout the timeout value in seconds. Default value (defined by + * SIP protocol) is used if {@code timeout} is zero or negative. * @see Listener.onError + * @throws SipException if the SIP service fails to answer the call */ - void answerCall(int timeout) throws SipException; + public synchronized void answerCall(int timeout) throws SipException { + stopRinging(); + try { + mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); + mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout); + } catch (IOException e) { + throw new SipException("answerCall()", e); + } + } /** * Continues a call that's on hold. When succeeds, @@ -219,45 +615,191 @@ public interface SipAudioCall { * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * - * @param timeout the timeout value in seconds + * @param timeout the timeout value in seconds. Default value (defined by + * SIP protocol) is used if {@code timeout} is zero or negative. * @see Listener.onError + * @throws SipException if the SIP service fails to unhold the call */ - void continueCall(int timeout) throws SipException; + public synchronized void continueCall(int timeout) throws SipException { + if (!mHold) return; + mSipSession.changeCall(createContinueOffer().encode(), timeout); + mHold = false; + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL); + } - /** Puts the device to speaker mode. */ - void setSpeakerMode(boolean speakerMode); + private SimpleSessionDescription createOffer() { + SimpleSessionDescription offer = + new SimpleSessionDescription(mSessionId, getLocalIp()); + AudioCodec[] codecs = AudioCodec.getCodecs(); + Media media = offer.newMedia( + "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); + for (AudioCodec codec : AudioCodec.getCodecs()) { + media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); + } + media.setRtpPayload(127, "telephone-event/8000", "0-15"); + return offer; + } - /** Toggles mute. */ - void toggleMute(); + private SimpleSessionDescription createAnswer(String offerSd) { + SimpleSessionDescription offer = + new SimpleSessionDescription(offerSd); + SimpleSessionDescription answer = + new SimpleSessionDescription(mSessionId, getLocalIp()); + AudioCodec codec = null; + for (Media media : offer.getMedia()) { + if ((codec == null) && (media.getPort() > 0) + && "audio".equals(media.getType()) + && "RTP/AVP".equals(media.getProtocol())) { + // Find the first audio codec we supported. + for (int type : media.getRtpPayloadTypes()) { + codec = AudioCodec.getCodec(type, media.getRtpmap(type), + media.getFmtp(type)); + if (codec != null) { + break; + } + } + if (codec != null) { + Media reply = answer.newMedia( + "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); + reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); - /** - * Checks if the call is on hold. - * - * @return true if the call is on hold - */ - boolean isOnHold(); + // Check if DTMF is supported in the same media. + for (int type : media.getRtpPayloadTypes()) { + String rtpmap = media.getRtpmap(type); + if ((type != codec.type) && (rtpmap != null) + && rtpmap.startsWith("telephone-event")) { + reply.setRtpPayload( + type, rtpmap, media.getFmtp(type)); + } + } + + // Handle recvonly and sendonly. + if (media.getAttribute("recvonly") != null) { + answer.setAttribute("sendonly", ""); + } else if(media.getAttribute("sendonly") != null) { + answer.setAttribute("recvonly", ""); + } else if(offer.getAttribute("recvonly") != null) { + answer.setAttribute("sendonly", ""); + } else if(offer.getAttribute("sendonly") != null) { + answer.setAttribute("recvonly", ""); + } + continue; + } + } + // Reject the media. + Media reply = answer.newMedia( + media.getType(), 0, 1, media.getProtocol()); + for (String format : media.getFormats()) { + reply.setFormat(format, null); + } + } + if (codec == null) { + throw new IllegalStateException("Reject SDP: no suitable codecs"); + } + return answer; + } + + private SimpleSessionDescription createHoldOffer() { + SimpleSessionDescription offer = createContinueOffer(); + offer.setAttribute("sendonly", ""); + return offer; + } + + private SimpleSessionDescription createContinueOffer() { + SimpleSessionDescription offer = + new SimpleSessionDescription(mSessionId, getLocalIp()); + Media media = offer.newMedia( + "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); + AudioCodec codec = mAudioStream.getCodec(); + media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); + int dtmfType = mAudioStream.getDtmfType(); + if (dtmfType != -1) { + media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15"); + } + return offer; + } + + private void grabWifiHighPerfLock() { + /* not available in master yet + if (mWifiHighPerfLock == null) { + Log.v(TAG, "acquire wifi high perf lock"); + mWifiHighPerfLock = ((WifiManager) + mContext.getSystemService(Context.WIFI_SERVICE)) + .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG); + mWifiHighPerfLock.acquire(); + } + */ + } + + private void releaseWifiHighPerfLock() { + if (mWifiHighPerfLock != null) { + Log.v(TAG, "release wifi high perf lock"); + mWifiHighPerfLock.release(); + mWifiHighPerfLock = null; + } + } + + private boolean isWifiOn() { + return (mWm.getConnectionInfo().getBSSID() == null) ? false : true; + } + + /** Toggles mute. */ + public synchronized void toggleMute() { + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) { + audioGroup.setMode( + mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED); + mMuted = !mMuted; + } + } /** * Checks if the call is muted. * * @return true if the call is muted */ - boolean isMuted(); + public synchronized boolean isMuted() { + return mMuted; + } + + /** Puts the device to speaker mode. */ + public synchronized void setSpeakerMode(boolean speakerMode) { + ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) + .setSpeakerphoneOn(speakerMode); + } /** - * Sends a DTMF code. + * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal + * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event + * flash to 16. Currently, event flash is not supported. * - * @param code the DTMF code to send + * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid + * inputs. + * @see http://tools.ietf.org/html/rfc2833 */ - void sendDtmf(int code); + public void sendDtmf(int code) { + sendDtmf(code, null); + } /** - * Sends a DTMF code. + * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal + * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event + * flash to 16. Currently, event flash is not supported. * - * @param code the DTMF code to send + * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid + * inputs. * @param result the result message to send when done */ - void sendDtmf(int code, Message result); + public synchronized void sendDtmf(int code, Message result) { + AudioGroup audioGroup = getAudioGroup(); + if ((audioGroup != null) && (mSipSession != null) + && (SipSession.State.IN_CALL == getState())) { + Log.v(TAG, "send DTMF: " + code); + audioGroup.sendDtmf(code); + } + if (result != null) result.sendToTarget(); + } /** * Gets the {@link AudioStream} object used in this call. The object @@ -268,8 +810,11 @@ public interface SipAudioCall { * * @return the {@link AudioStream} object or null if the RTP stream has not * yet been set up + * @hide */ - AudioStream getAudioStream(); + public synchronized AudioStream getAudioStream() { + return mAudioStream; + } /** * Gets the {@link AudioGroup} object which the {@link AudioStream} object @@ -283,8 +828,12 @@ public interface SipAudioCall { * @return the {@link AudioGroup} object or null if the RTP stream has not * yet been set up * @see #getAudioStream + * @hide */ - AudioGroup getAudioGroup(); + public synchronized AudioGroup getAudioGroup() { + if (mAudioGroup != null) return mAudioGroup; + return ((mAudioStream == null) ? null : mAudioStream.getGroup()); + } /** * Sets the {@link AudioGroup} object which the {@link AudioStream} object @@ -292,56 +841,214 @@ public interface SipAudioCall { * will be dynamically created when needed. * * @see #getAudioStream + * @hide */ - void setAudioGroup(AudioGroup audioGroup); + public synchronized void setAudioGroup(AudioGroup group) { + if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { + mAudioStream.join(group); + } + mAudioGroup = group; + } /** - * Checks if the call is established. - * - * @return true if the call is established + * Starts the audio for the established call. This method should be called + * after {@link Listener#onCallEstablished} is called. */ - boolean isInCall(); + public void startAudio() { + try { + startAudioInternal(); + } catch (UnknownHostException e) { + onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage()); + } catch (Throwable e) { + onError(SipErrorCode.CLIENT_ERROR, e.getMessage()); + } + } - /** - * Gets the local SIP profile. - * - * @return the local SIP profile - */ - SipProfile getLocalProfile(); + private synchronized void startAudioInternal() throws UnknownHostException { + if (mPeerSd == null) { + Log.v(TAG, "startAudioInternal() mPeerSd = null"); + throw new IllegalStateException("mPeerSd = null"); + } - /** - * Gets the peer's SIP profile. - * - * @return the peer's SIP profile - */ - SipProfile getPeerProfile(); + stopCall(DONT_RELEASE_SOCKET); + mInCall = true; - /** - * Gets the state of the {@link ISipSession} that carries this call. - * The value returned must be one of the states in {@link SipSessionState}. - * - * @return the session state - */ - int getState(); + // Run exact the same logic in createAnswer() to setup mAudioStream. + SimpleSessionDescription offer = + new SimpleSessionDescription(mPeerSd); + AudioStream stream = mAudioStream; + AudioCodec codec = null; + for (Media media : offer.getMedia()) { + if ((codec == null) && (media.getPort() > 0) + && "audio".equals(media.getType()) + && "RTP/AVP".equals(media.getProtocol())) { + // Find the first audio codec we supported. + for (int type : media.getRtpPayloadTypes()) { + codec = AudioCodec.getCodec( + type, media.getRtpmap(type), media.getFmtp(type)); + if (codec != null) { + break; + } + } + + if (codec != null) { + // Associate with the remote host. + String address = media.getAddress(); + if (address == null) { + address = offer.getAddress(); + } + stream.associate(InetAddress.getByName(address), + media.getPort()); + + stream.setDtmfType(-1); + stream.setCodec(codec); + // Check if DTMF is supported in the same media. + for (int type : media.getRtpPayloadTypes()) { + String rtpmap = media.getRtpmap(type); + if ((type != codec.type) && (rtpmap != null) + && rtpmap.startsWith("telephone-event")) { + stream.setDtmfType(type); + } + } + + // Handle recvonly and sendonly. + if (mHold) { + stream.setMode(RtpStream.MODE_NORMAL); + } else if (media.getAttribute("recvonly") != null) { + stream.setMode(RtpStream.MODE_SEND_ONLY); + } else if(media.getAttribute("sendonly") != null) { + stream.setMode(RtpStream.MODE_RECEIVE_ONLY); + } else if(offer.getAttribute("recvonly") != null) { + stream.setMode(RtpStream.MODE_SEND_ONLY); + } else if(offer.getAttribute("sendonly") != null) { + stream.setMode(RtpStream.MODE_RECEIVE_ONLY); + } else { + stream.setMode(RtpStream.MODE_NORMAL); + } + break; + } + } + } + if (codec == null) { + throw new IllegalStateException("Reject SDP: no suitable codecs"); + } + + if (isWifiOn()) grabWifiHighPerfLock(); + + if (!mHold) { + /* The recorder volume will be very low if the device is in + * IN_CALL mode. Therefore, we have to set the mode to NORMAL + * in order to have the normal microphone level. + */ + ((AudioManager) mContext.getSystemService + (Context.AUDIO_SERVICE)) + .setMode(AudioManager.MODE_NORMAL); + } + + // AudioGroup logic: + AudioGroup audioGroup = getAudioGroup(); + if (mHold) { + if (audioGroup != null) { + audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + } + // don't create an AudioGroup here; doing so will fail if + // there's another AudioGroup out there that's active + } else { + if (audioGroup == null) audioGroup = new AudioGroup(); + stream.join(audioGroup); + if (mMuted) { + audioGroup.setMode(AudioGroup.MODE_MUTED); + } else { + audioGroup.setMode(AudioGroup.MODE_NORMAL); + } + } + } + + private void stopCall(boolean releaseSocket) { + Log.d(TAG, "stop audiocall"); + releaseWifiHighPerfLock(); + if (mAudioStream != null) { + mAudioStream.join(null); + + if (releaseSocket) { + mAudioStream.release(); + mAudioStream = null; + } + } + } + + private String getLocalIp() { + return mSipSession.getLocalIp(); + } - /** - * Gets the {@link ISipSession} that carries this call. - * - * @return the session object that carries this call - */ - ISipSession getSipSession(); /** * Enables/disables the ring-back tone. * * @param enabled true to enable; false to disable */ - void setRingbackToneEnabled(boolean enabled); + public synchronized void setRingbackToneEnabled(boolean enabled) { + mRingbackToneEnabled = enabled; + } /** * Enables/disables the ring tone. * * @param enabled true to enable; false to disable */ - void setRingtoneEnabled(boolean enabled); + public synchronized void setRingtoneEnabled(boolean enabled) { + mRingtoneEnabled = enabled; + } + + private void startRingbackTone() { + if (!mRingbackToneEnabled) return; + if (mRingbackTone == null) { + // The volume relative to other sounds in the stream + int toneVolume = 80; + mRingbackTone = new ToneGenerator( + AudioManager.STREAM_VOICE_CALL, toneVolume); + } + mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L); + } + + private void stopRingbackTone() { + if (mRingbackTone != null) { + mRingbackTone.stopTone(); + mRingbackTone.release(); + mRingbackTone = null; + } + } + + private void startRinging() { + if (!mRingtoneEnabled) return; + ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)) + .vibrate(new long[] {0, 1000, 1000}, 1); + AudioManager am = (AudioManager) + mContext.getSystemService(Context.AUDIO_SERVICE); + if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) { + String ringtoneUri = + Settings.System.DEFAULT_RINGTONE_URI.toString(); + mRingtone = RingtoneManager.getRingtone(mContext, + Uri.parse(ringtoneUri)); + mRingtone.play(); + } + } + + private void stopRinging() { + ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)) + .cancel(); + if (mRingtone != null) mRingtone.stop(); + } + + private void throwSipException(Throwable throwable) throws SipException { + if (throwable instanceof SipException) { + throw (SipException) throwable; + } else { + throw new SipException("", throwable); + } + } + + private SipProfile getPeerProfile(SipSession session) { + return session.getPeerProfile(); + } } diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java deleted file mode 100644 index 5eecc05..0000000 --- a/voip/java/android/net/sip/SipAudioCallImpl.java +++ /dev/null @@ -1,738 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.sip; - -import android.content.Context; -import android.media.AudioManager; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.media.ToneGenerator; -import android.net.Uri; -import android.net.rtp.AudioCodec; -import android.net.rtp.AudioGroup; -import android.net.rtp.AudioStream; -import android.net.rtp.RtpStream; -import android.net.sip.SimpleSessionDescription.Media; -import android.net.wifi.WifiManager; -import android.os.Message; -import android.os.RemoteException; -import android.os.Vibrator; -import android.provider.Settings; -import android.util.Log; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Class that handles an audio call over SIP. - */ -/** @hide */ -public class SipAudioCallImpl extends SipSessionAdapter - implements SipAudioCall { - private static final String TAG = SipAudioCallImpl.class.getSimpleName(); - private static final boolean RELEASE_SOCKET = true; - private static final boolean DONT_RELEASE_SOCKET = false; - private static final int SESSION_TIMEOUT = 5; // in seconds - - private Context mContext; - private SipProfile mLocalProfile; - private SipAudioCall.Listener mListener; - private ISipSession mSipSession; - - private long mSessionId = System.currentTimeMillis(); - private String mPeerSd; - - private AudioStream mAudioStream; - private AudioGroup mAudioGroup; - - private boolean mInCall = false; - private boolean mMuted = false; - private boolean mHold = false; - - private boolean mRingbackToneEnabled = true; - private boolean mRingtoneEnabled = true; - private Ringtone mRingtone; - private ToneGenerator mRingbackTone; - - private SipProfile mPendingCallRequest; - - private int mErrorCode = SipErrorCode.NO_ERROR; - private String mErrorMessage; - - public SipAudioCallImpl(Context context, SipProfile localProfile) { - mContext = context; - mLocalProfile = localProfile; - } - - public void setListener(SipAudioCall.Listener listener) { - setListener(listener, false); - } - - public void setListener(SipAudioCall.Listener listener, - boolean callbackImmediately) { - mListener = listener; - try { - if ((listener == null) || !callbackImmediately) { - // do nothing - } else if (mErrorCode != SipErrorCode.NO_ERROR) { - listener.onError(this, mErrorCode, mErrorMessage); - } else if (mInCall) { - if (mHold) { - listener.onCallHeld(this); - } else { - listener.onCallEstablished(this); - } - } else { - int state = getState(); - switch (state) { - case SipSessionState.READY_TO_CALL: - listener.onReadyToCall(this); - break; - case SipSessionState.INCOMING_CALL: - listener.onRinging(this, getPeerProfile(mSipSession)); - break; - case SipSessionState.OUTGOING_CALL: - listener.onCalling(this); - break; - case SipSessionState.OUTGOING_CALL_RING_BACK: - listener.onRingingBack(this); - break; - } - } - } catch (Throwable t) { - Log.e(TAG, "setListener()", t); - } - } - - public synchronized boolean isInCall() { - return mInCall; - } - - public synchronized boolean isOnHold() { - return mHold; - } - - public void close() { - close(true); - } - - private synchronized void close(boolean closeRtp) { - if (closeRtp) stopCall(RELEASE_SOCKET); - stopRingbackTone(); - stopRinging(); - - mInCall = false; - mHold = false; - mSessionId = System.currentTimeMillis(); - mErrorCode = SipErrorCode.NO_ERROR; - mErrorMessage = null; - - if (mSipSession != null) { - try { - mSipSession.setListener(null); - } catch (RemoteException e) { - // don't care - } - mSipSession = null; - } - } - - public synchronized SipProfile getLocalProfile() { - return mLocalProfile; - } - - public synchronized SipProfile getPeerProfile() { - try { - return (mSipSession == null) ? null : mSipSession.getPeerProfile(); - } catch (RemoteException e) { - return null; - } - } - - public synchronized int getState() { - if (mSipSession == null) return SipSessionState.READY_TO_CALL; - try { - return mSipSession.getState(); - } catch (RemoteException e) { - return SipSessionState.REMOTE_ERROR; - } - } - - - public synchronized ISipSession getSipSession() { - return mSipSession; - } - - @Override - public void onCalling(ISipSession session) { - Log.d(TAG, "calling... " + session); - Listener listener = mListener; - if (listener != null) { - try { - listener.onCalling(this); - } catch (Throwable t) { - Log.e(TAG, "onCalling()", t); - } - } - } - - @Override - public void onRingingBack(ISipSession session) { - Log.d(TAG, "sip call ringing back: " + session); - if (!mInCall) startRingbackTone(); - Listener listener = mListener; - if (listener != null) { - try { - listener.onRingingBack(this); - } catch (Throwable t) { - Log.e(TAG, "onRingingBack()", t); - } - } - } - - @Override - public synchronized void onRinging(ISipSession session, - SipProfile peerProfile, String sessionDescription) { - try { - if ((mSipSession == null) || !mInCall - || !session.getCallId().equals(mSipSession.getCallId())) { - // should not happen - session.endCall(); - return; - } - - // session changing request - try { - String answer = createAnswer(sessionDescription).encode(); - mSipSession.answerCall(answer, SESSION_TIMEOUT); - } catch (Throwable e) { - Log.e(TAG, "onRinging()", e); - session.endCall(); - } - } catch (RemoteException e) { - Log.e(TAG, "onRinging()", e); - } - } - - @Override - public void onCallEstablished(ISipSession session, - String sessionDescription) { - stopRingbackTone(); - stopRinging(); - mPeerSd = sessionDescription; - Log.v(TAG, "onCallEstablished()" + mPeerSd); - - Listener listener = mListener; - if (listener != null) { - try { - if (mHold) { - listener.onCallHeld(this); - } else { - listener.onCallEstablished(this); - } - } catch (Throwable t) { - Log.e(TAG, "onCallEstablished()", t); - } - } - } - - @Override - public void onCallEnded(ISipSession session) { - Log.d(TAG, "sip call ended: " + session); - Listener listener = mListener; - if (listener != null) { - try { - listener.onCallEnded(this); - } catch (Throwable t) { - Log.e(TAG, "onCallEnded()", t); - } - } - close(); - } - - @Override - public void onCallBusy(ISipSession session) { - Log.d(TAG, "sip call busy: " + session); - Listener listener = mListener; - if (listener != null) { - try { - listener.onCallBusy(this); - } catch (Throwable t) { - Log.e(TAG, "onCallBusy()", t); - } - } - close(false); - } - - @Override - public void onCallChangeFailed(ISipSession session, int errorCode, - String message) { - Log.d(TAG, "sip call change failed: " + message); - mErrorCode = errorCode; - mErrorMessage = message; - Listener listener = mListener; - if (listener != null) { - try { - listener.onError(this, mErrorCode, message); - } catch (Throwable t) { - Log.e(TAG, "onCallBusy()", t); - } - } - } - - @Override - public void onError(ISipSession session, int errorCode, String message) { - Log.d(TAG, "sip session error: " + SipErrorCode.toString(errorCode) - + ": " + message); - mErrorCode = errorCode; - mErrorMessage = message; - Listener listener = mListener; - if (listener != null) { - try { - listener.onError(this, errorCode, message); - } catch (Throwable t) { - Log.e(TAG, "onError()", t); - } - } - synchronized (this) { - if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST) - || !isInCall()) { - close(true); - } - } - } - - public synchronized void attachCall(ISipSession session, - String sessionDescription) throws SipException { - mSipSession = session; - mPeerSd = sessionDescription; - Log.v(TAG, "attachCall()" + mPeerSd); - try { - session.setListener(this); - if (getState() == SipSessionState.INCOMING_CALL) startRinging(); - } catch (Throwable e) { - Log.e(TAG, "attachCall()", e); - throwSipException(e); - } - } - - public synchronized void makeCall(SipProfile peerProfile, - SipManager sipManager, int timeout) throws SipException { - try { - mSipSession = sipManager.createSipSession(mLocalProfile, this); - if (mSipSession == null) { - throw new SipException( - "Failed to create SipSession; network available?"); - } - mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); - mSipSession.makeCall(peerProfile, createOffer().encode(), timeout); - } catch (Throwable e) { - if (e instanceof SipException) { - throw (SipException) e; - } else { - throwSipException(e); - } - } - } - - public synchronized void endCall() throws SipException { - try { - stopRinging(); - stopCall(RELEASE_SOCKET); - mInCall = false; - - // perform the above local ops first and then network op - if (mSipSession != null) mSipSession.endCall(); - } catch (Throwable e) { - throwSipException(e); - } - } - - public synchronized void answerCall(int timeout) throws SipException { - try { - stopRinging(); - mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); - mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout); - } catch (Throwable e) { - Log.e(TAG, "answerCall()", e); - throwSipException(e); - } - } - - public synchronized void holdCall(int timeout) throws SipException { - if (mHold) return; - try { - mSipSession.changeCall(createHoldOffer().encode(), timeout); - } catch (Throwable e) { - throwSipException(e); - } - mHold = true; - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); - } - - public synchronized void continueCall(int timeout) throws SipException { - if (!mHold) return; - try { - mSipSession.changeCall(createContinueOffer().encode(), timeout); - } catch (Throwable e) { - throwSipException(e); - } - mHold = false; - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL); - } - - private SimpleSessionDescription createOffer() { - SimpleSessionDescription offer = - new SimpleSessionDescription(mSessionId, getLocalIp()); - AudioCodec[] codecs = AudioCodec.getCodecs(); - Media media = offer.newMedia( - "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); - for (AudioCodec codec : AudioCodec.getCodecs()) { - media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); - } - media.setRtpPayload(127, "telephone-event/8000", "0-15"); - return offer; - } - - private SimpleSessionDescription createAnswer(String offerSd) { - SimpleSessionDescription offer = - new SimpleSessionDescription(offerSd); - SimpleSessionDescription answer = - new SimpleSessionDescription(mSessionId, getLocalIp()); - AudioCodec codec = null; - for (Media media : offer.getMedia()) { - if ((codec == null) && (media.getPort() > 0) - && "audio".equals(media.getType()) - && "RTP/AVP".equals(media.getProtocol())) { - // Find the first audio codec we supported. - for (int type : media.getRtpPayloadTypes()) { - codec = AudioCodec.getCodec(type, media.getRtpmap(type), - media.getFmtp(type)); - if (codec != null) { - break; - } - } - if (codec != null) { - Media reply = answer.newMedia( - "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); - reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); - - // Check if DTMF is supported in the same media. - for (int type : media.getRtpPayloadTypes()) { - String rtpmap = media.getRtpmap(type); - if ((type != codec.type) && (rtpmap != null) - && rtpmap.startsWith("telephone-event")) { - reply.setRtpPayload( - type, rtpmap, media.getFmtp(type)); - } - } - - // Handle recvonly and sendonly. - if (media.getAttribute("recvonly") != null) { - answer.setAttribute("sendonly", ""); - } else if(media.getAttribute("sendonly") != null) { - answer.setAttribute("recvonly", ""); - } else if(offer.getAttribute("recvonly") != null) { - answer.setAttribute("sendonly", ""); - } else if(offer.getAttribute("sendonly") != null) { - answer.setAttribute("recvonly", ""); - } - continue; - } - } - // Reject the media. - Media reply = answer.newMedia( - media.getType(), 0, 1, media.getProtocol()); - for (String format : media.getFormats()) { - reply.setFormat(format, null); - } - } - if (codec == null) { - throw new IllegalStateException("Reject SDP: no suitable codecs"); - } - return answer; - } - - private SimpleSessionDescription createHoldOffer() { - SimpleSessionDescription offer = createContinueOffer(); - offer.setAttribute("sendonly", ""); - return offer; - } - - private SimpleSessionDescription createContinueOffer() { - SimpleSessionDescription offer = - new SimpleSessionDescription(mSessionId, getLocalIp()); - Media media = offer.newMedia( - "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP"); - AudioCodec codec = mAudioStream.getCodec(); - media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); - int dtmfType = mAudioStream.getDtmfType(); - if (dtmfType != -1) { - media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15"); - } - return offer; - } - - public synchronized void toggleMute() { - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) { - audioGroup.setMode( - mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED); - mMuted = !mMuted; - } - } - - public synchronized boolean isMuted() { - return mMuted; - } - - public synchronized void setSpeakerMode(boolean speakerMode) { - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setSpeakerphoneOn(speakerMode); - } - - public void sendDtmf(int code) { - sendDtmf(code, null); - } - - public synchronized void sendDtmf(int code, Message result) { - AudioGroup audioGroup = getAudioGroup(); - if ((audioGroup != null) && (mSipSession != null) - && (SipSessionState.IN_CALL == getState())) { - Log.v(TAG, "send DTMF: " + code); - audioGroup.sendDtmf(code); - } - if (result != null) result.sendToTarget(); - } - - public synchronized AudioStream getAudioStream() { - return mAudioStream; - } - - public synchronized AudioGroup getAudioGroup() { - if (mAudioGroup != null) return mAudioGroup; - return ((mAudioStream == null) ? null : mAudioStream.getGroup()); - } - - public synchronized void setAudioGroup(AudioGroup group) { - if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { - mAudioStream.join(group); - } - mAudioGroup = group; - } - - public void startAudio() { - try { - startAudioInternal(); - } catch (UnknownHostException e) { - onError(mSipSession, SipErrorCode.PEER_NOT_REACHABLE, - e.getMessage()); - } catch (Throwable e) { - onError(mSipSession, SipErrorCode.CLIENT_ERROR, - e.getMessage()); - } - } - - private synchronized void startAudioInternal() throws UnknownHostException { - if (mPeerSd == null) { - Log.v(TAG, "startAudioInternal() mPeerSd = null"); - throw new IllegalStateException("mPeerSd = null"); - } - - stopCall(DONT_RELEASE_SOCKET); - mInCall = true; - - // Run exact the same logic in createAnswer() to setup mAudioStream. - SimpleSessionDescription offer = - new SimpleSessionDescription(mPeerSd); - AudioStream stream = mAudioStream; - AudioCodec codec = null; - for (Media media : offer.getMedia()) { - if ((codec == null) && (media.getPort() > 0) - && "audio".equals(media.getType()) - && "RTP/AVP".equals(media.getProtocol())) { - // Find the first audio codec we supported. - for (int type : media.getRtpPayloadTypes()) { - codec = AudioCodec.getCodec( - type, media.getRtpmap(type), media.getFmtp(type)); - if (codec != null) { - break; - } - } - - if (codec != null) { - // Associate with the remote host. - String address = media.getAddress(); - if (address == null) { - address = offer.getAddress(); - } - stream.associate(InetAddress.getByName(address), - media.getPort()); - - stream.setDtmfType(-1); - stream.setCodec(codec); - // Check if DTMF is supported in the same media. - for (int type : media.getRtpPayloadTypes()) { - String rtpmap = media.getRtpmap(type); - if ((type != codec.type) && (rtpmap != null) - && rtpmap.startsWith("telephone-event")) { - stream.setDtmfType(type); - } - } - - // Handle recvonly and sendonly. - if (mHold) { - stream.setMode(RtpStream.MODE_NORMAL); - } else if (media.getAttribute("recvonly") != null) { - stream.setMode(RtpStream.MODE_SEND_ONLY); - } else if(media.getAttribute("sendonly") != null) { - stream.setMode(RtpStream.MODE_RECEIVE_ONLY); - } else if(offer.getAttribute("recvonly") != null) { - stream.setMode(RtpStream.MODE_SEND_ONLY); - } else if(offer.getAttribute("sendonly") != null) { - stream.setMode(RtpStream.MODE_RECEIVE_ONLY); - } else { - stream.setMode(RtpStream.MODE_NORMAL); - } - break; - } - } - } - if (codec == null) { - throw new IllegalStateException("Reject SDP: no suitable codecs"); - } - - if (!mHold) { - /* The recorder volume will be very low if the device is in - * IN_CALL mode. Therefore, we have to set the mode to NORMAL - * in order to have the normal microphone level. - */ - ((AudioManager) mContext.getSystemService - (Context.AUDIO_SERVICE)) - .setMode(AudioManager.MODE_NORMAL); - } - - // AudioGroup logic: - AudioGroup audioGroup = getAudioGroup(); - if (mHold) { - if (audioGroup != null) { - audioGroup.setMode(AudioGroup.MODE_ON_HOLD); - } - // don't create an AudioGroup here; doing so will fail if - // there's another AudioGroup out there that's active - } else { - if (audioGroup == null) audioGroup = new AudioGroup(); - mAudioStream.join(audioGroup); - if (mMuted) { - audioGroup.setMode(AudioGroup.MODE_MUTED); - } else { - audioGroup.setMode(AudioGroup.MODE_NORMAL); - } - } - } - - private void stopCall(boolean releaseSocket) { - Log.d(TAG, "stop audiocall"); - if (mAudioStream != null) { - mAudioStream.join(null); - - if (releaseSocket) { - mAudioStream.release(); - mAudioStream = null; - } - } - } - - private String getLocalIp() { - try { - return mSipSession.getLocalIp(); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } - } - - public synchronized void setRingbackToneEnabled(boolean enabled) { - mRingbackToneEnabled = enabled; - } - - public synchronized void setRingtoneEnabled(boolean enabled) { - mRingtoneEnabled = enabled; - } - - private void startRingbackTone() { - if (!mRingbackToneEnabled) return; - if (mRingbackTone == null) { - // The volume relative to other sounds in the stream - int toneVolume = 80; - mRingbackTone = new ToneGenerator( - AudioManager.STREAM_VOICE_CALL, toneVolume); - } - mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L); - } - - private void stopRingbackTone() { - if (mRingbackTone != null) { - mRingbackTone.stopTone(); - mRingbackTone.release(); - mRingbackTone = null; - } - } - - private void startRinging() { - if (!mRingtoneEnabled) return; - ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)) - .vibrate(new long[] {0, 1000, 1000}, 1); - AudioManager am = (AudioManager) - mContext.getSystemService(Context.AUDIO_SERVICE); - if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) { - String ringtoneUri = - Settings.System.DEFAULT_RINGTONE_URI.toString(); - mRingtone = RingtoneManager.getRingtone(mContext, - Uri.parse(ringtoneUri)); - mRingtone.play(); - } - } - - private void stopRinging() { - ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)) - .cancel(); - if (mRingtone != null) mRingtone.stop(); - } - - private void throwSipException(Throwable throwable) throws SipException { - if (throwable instanceof SipException) { - throw (SipException) throwable; - } else { - throw new SipException("", throwable); - } - } - - private SipProfile getPeerProfile(ISipSession session) { - try { - return session.getPeerProfile(); - } catch (RemoteException e) { - return null; - } - } -} diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java index 31768d7..5976a04 100644 --- a/voip/java/android/net/sip/SipManager.java +++ b/voip/java/android/net/sip/SipManager.java @@ -30,8 +30,9 @@ import java.text.ParseException; * The class provides API for various SIP related tasks. Specifically, the API * allows an application to: * <ul> - * <li>register a {@link SipProfile} to have the background SIP service listen - * to incoming calls and broadcast them with registered command string. See + * <li>open a {@link SipProfile} to get ready for making outbound calls or have + * the background SIP service listen to incoming calls and broadcast them + * with registered command string. See * {@link #open(SipProfile, String, SipRegistrationListener)}, * {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and * {@link #isRegistered}. It also facilitates handling of the incoming call @@ -40,39 +41,59 @@ import java.text.ParseException; * {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li> * <li>make/take SIP-based audio calls. See * {@link #makeAudioCall} and {@link #takeAudioCall}.</li> - * <li>register/unregister with a SIP service provider. See + * <li>register/unregister with a SIP service provider manually. See * {@link #register} and {@link #unregister}.</li> - * <li>process SIP events directly with a {@link ISipSession} created by + * <li>process SIP events directly with a {@link SipSession} created by * {@link #createSipSession}.</li> * </ul> * @hide */ public class SipManager { - /** @hide */ - public static final String SIP_INCOMING_CALL_ACTION = + /** + * Action string for the incoming call intent for the Phone app. + * Internal use only. + * @hide + */ + public static final String ACTION_SIP_INCOMING_CALL = "com.android.phone.SIP_INCOMING_CALL"; - /** @hide */ - public static final String SIP_ADD_PHONE_ACTION = + /** + * Action string for the add-phone intent. + * Internal use only. + * @hide + */ + public static final String ACTION_SIP_ADD_PHONE = "com.android.phone.SIP_ADD_PHONE"; - /** @hide */ - public static final String SIP_REMOVE_PHONE_ACTION = + /** + * Action string for the remove-phone intent. + * Internal use only. + * @hide + */ + public static final String ACTION_SIP_REMOVE_PHONE = "com.android.phone.SIP_REMOVE_PHONE"; - /** @hide */ - public static final String LOCAL_URI_KEY = "LOCAL SIPURI"; + /** + * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents. + * Internal use only. + * @hide + */ + public static final String EXTRA_LOCAL_URI = "android:localSipUri"; - private static final String CALL_ID_KEY = "CallID"; - private static final String OFFER_SD_KEY = "OfferSD"; + /** Part of the incoming call intent. */ + public static final String EXTRA_CALL_ID = "android:sipCallID"; + + /** Part of the incoming call intent. */ + public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; private ISipService mSipService; + private Context mContext; /** - * Gets a manager instance. Returns null if SIP API is not supported. + * Creates a manager instance. Returns null if SIP API is not supported. * - * @param context application context for checking if SIP API is supported + * @param context application context for creating the manager object * @return the manager instance or null if SIP API is not supported */ - public static SipManager getInstance(Context context) { - return (isApiSupported(context) ? new SipManager() : null); + public static SipManager newInstance(Context context) { + return (isApiSupported(context) ? new SipManager(context) : null); } /** @@ -80,7 +101,7 @@ public class SipManager { */ public static boolean isApiSupported(Context context) { return true; - /* + /* TODO: uncomment this before ship return context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_SIP); */ @@ -91,7 +112,7 @@ public class SipManager { */ public static boolean isVoipSupported(Context context) { return true; - /* + /* TODO: uncomment this before ship return context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context); */ @@ -105,23 +126,21 @@ public class SipManager { com.android.internal.R.bool.config_sip_wifi_only); } - private SipManager() { + private SipManager(Context context) { + mContext = context; createSipService(); } private void createSipService() { - if (mSipService != null) return; IBinder b = ServiceManager.getService(Context.SIP_SERVICE); mSipService = ISipService.Stub.asInterface(b); } /** - * Opens the profile for making calls and/or receiving calls. Subsequent - * SIP calls can be made through the default phone UI. The caller may also - * make subsequent calls through {@link #makeAudioCall}. - * If the receiving-call option is enabled in the profile, the SIP service - * will register the profile to the corresponding server periodically in - * order to receive calls from the server. + * Opens the profile for making calls. The caller may make subsequent calls + * through {@link #makeAudioCall}. If one also wants to receive calls on the + * profile, use {@link #open(SipProfile, String, SipRegistrationListener)} + * instead. * * @param localProfile the SIP profile to make calls from * @throws SipException if the profile contains incorrect settings or @@ -136,12 +155,11 @@ public class SipManager { } /** - * Opens the profile for making calls and/or receiving calls. Subsequent - * SIP calls can be made through the default phone UI. The caller may also - * make subsequent calls through {@link #makeAudioCall}. - * If the receiving-call option is enabled in the profile, the SIP service - * will register the profile to the corresponding server periodically in - * order to receive calls from the server. + * Opens the profile for making calls and/or receiving calls. The caller may + * make subsequent calls through {@link #makeAudioCall}. If the + * auto-registration option is enabled in the profile, the SIP service + * will register the profile to the corresponding SIP provider periodically + * in order to receive calls from the provider. * * @param localProfile the SIP profile to receive incoming calls for * @param incomingCallBroadcastAction the action to be broadcast when an @@ -195,7 +213,8 @@ public class SipManager { } /** - * Checks if the specified profile is enabled to receive calls. + * Checks if the specified profile is opened in the SIP service for + * making and/or receiving calls. * * @param localProfileUri the URI of the profile in question * @return true if the profile is enabled to receive calls @@ -210,11 +229,16 @@ public class SipManager { } /** - * Checks if the specified profile is registered to the server for - * receiving calls. + * Checks if the SIP service has successfully registered the profile to the + * SIP provider (specified in the profile) for receiving calls. Returning + * true from this method also implies the profile is opened + * ({@link #isOpened}). * * @param localProfileUri the URI of the profile in question - * @return true if the profile is registered to the server + * @return true if the profile is registered to the SIP provider; false if + * the profile has not been opened in the SIP service or the SIP + * service has not yet successfully registered the profile to the SIP + * provider * @throws SipException if calling the SIP service results in an error */ public boolean isRegistered(String localProfileUri) throws SipException { @@ -231,7 +255,6 @@ public class SipManager { * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * - * @param context context to create a {@link SipAudioCall} object * @param localProfile the SIP profile to make the call from * @param peerProfile the SIP profile to make the call to * @param listener to listen to the call events from {@link SipAudioCall}; @@ -241,10 +264,10 @@ public class SipManager { * @throws SipException if calling the SIP service results in an error * @see SipAudioCall.Listener.onError */ - public SipAudioCall makeAudioCall(Context context, SipProfile localProfile, + public SipAudioCall makeAudioCall(SipProfile localProfile, SipProfile peerProfile, SipAudioCall.Listener listener, int timeout) throws SipException { - SipAudioCall call = new SipAudioCallImpl(context, localProfile); + SipAudioCall call = new SipAudioCall(mContext, localProfile); call.setListener(listener); call.makeCall(peerProfile, this, timeout); return call; @@ -257,7 +280,6 @@ public class SipManager { * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * - * @param context context to create a {@link SipAudioCall} object * @param localProfileUri URI of the SIP profile to make the call from * @param peerProfileUri URI of the SIP profile to make the call to * @param listener to listen to the call events from {@link SipAudioCall}; @@ -267,11 +289,11 @@ public class SipManager { * @throws SipException if calling the SIP service results in an error * @see SipAudioCall.Listener.onError */ - public SipAudioCall makeAudioCall(Context context, String localProfileUri, + public SipAudioCall makeAudioCall(String localProfileUri, String peerProfileUri, SipAudioCall.Listener listener, int timeout) throws SipException { try { - return makeAudioCall(context, + return makeAudioCall( new SipProfile.Builder(localProfileUri).build(), new SipProfile.Builder(peerProfileUri).build(), listener, timeout); @@ -281,15 +303,14 @@ public class SipManager { } /** - * The method calls {@code takeAudioCall(context, incomingCallIntent, + * The method calls {@code takeAudioCall(incomingCallIntent, * listener, true}. * - * @see #takeAudioCall(Context, Intent, SipAudioCall.Listener, boolean) + * @see #takeAudioCall(Intent, SipAudioCall.Listener, boolean) */ - public SipAudioCall takeAudioCall(Context context, - Intent incomingCallIntent, SipAudioCall.Listener listener) - throws SipException { - return takeAudioCall(context, incomingCallIntent, listener, true); + public SipAudioCall takeAudioCall(Intent incomingCallIntent, + SipAudioCall.Listener listener) throws SipException { + return takeAudioCall(incomingCallIntent, listener, true); } /** @@ -298,16 +319,15 @@ public class SipManager { * {@link SipAudioCall.Listener#onRinging} * callback. * - * @param context context to create a {@link SipAudioCall} object * @param incomingCallIntent the incoming call broadcast intent * @param listener to listen to the call events from {@link SipAudioCall}; * can be null * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error */ - public SipAudioCall takeAudioCall(Context context, - Intent incomingCallIntent, SipAudioCall.Listener listener, - boolean ringtoneEnabled) throws SipException { + public SipAudioCall takeAudioCall(Intent incomingCallIntent, + SipAudioCall.Listener listener, boolean ringtoneEnabled) + throws SipException { if (incomingCallIntent == null) return null; String callId = getCallId(incomingCallIntent); @@ -324,10 +344,10 @@ public class SipManager { try { ISipSession session = mSipService.getPendingSession(callId); if (session == null) return null; - SipAudioCall call = new SipAudioCallImpl( - context, session.getLocalProfile()); + SipAudioCall call = new SipAudioCall( + mContext, session.getLocalProfile()); call.setRingtoneEnabled(ringtoneEnabled); - call.attachCall(session, offerSd); + call.attachCall(new SipSession(session), offerSd); call.setListener(listener); return call; } catch (Throwable t) { @@ -355,7 +375,7 @@ public class SipManager { * @return the call ID or null if the intent does not contain it */ public static String getCallId(Intent incomingCallIntent) { - return incomingCallIntent.getStringExtra(CALL_ID_KEY); + return incomingCallIntent.getStringExtra(EXTRA_CALL_ID); } /** @@ -367,30 +387,30 @@ public class SipManager { * have it */ public static String getOfferSessionDescription(Intent incomingCallIntent) { - return incomingCallIntent.getStringExtra(OFFER_SD_KEY); + return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD); } /** * Creates an incoming call broadcast intent. * - * @param action the action string to broadcast * @param callId the call ID of the incoming call * @param sessionDescription the session description of the incoming call * @return the incoming call intent * @hide */ - public static Intent createIncomingCallBroadcast(String action, - String callId, String sessionDescription) { - Intent intent = new Intent(action); - intent.putExtra(CALL_ID_KEY, callId); - intent.putExtra(OFFER_SD_KEY, sessionDescription); + public static Intent createIncomingCallBroadcast(String callId, + String sessionDescription) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_CALL_ID, callId); + intent.putExtra(EXTRA_OFFER_SD, sessionDescription); return intent; } /** - * Registers the profile to the corresponding server for receiving calls. - * {@link #open} is still needed to be called at least once in order for - * the SIP service to broadcast an intent when an incoming call is received. + * Manually registers the profile to the corresponding SIP provider for + * receiving calls. {@link #open(SipProfile, String, SipRegistrationListener)} + * is still needed to be called at least once in order for the SIP service + * to broadcast an intent when an incoming call is received. * * @param localProfile the SIP profile to register with * @param expiryTime registration expiration time (in seconds) @@ -409,8 +429,10 @@ public class SipManager { } /** - * Unregisters the profile from the corresponding server for not receiving - * further calls. + * Manually unregisters the profile from the corresponding SIP provider for + * stop receiving further calls. This may interference with the auto + * registration process in the SIP service if the auto-registration option + * in the profile is enabled. * * @param localProfile the SIP profile to register with * @param listener to listen to the registration events @@ -460,10 +482,11 @@ public class SipManager { * @param localProfile the SIP profile the session is associated with * @param listener to listen to SIP session events */ - public ISipSession createSipSession(SipProfile localProfile, - ISipSessionListener listener) throws SipException { + public SipSession createSipSession(SipProfile localProfile, + SipSession.Listener listener) throws SipException { try { - return mSipService.createSession(localProfile, listener); + ISipSession s = mSipService.createSession(localProfile, null); + return new SipSession(s, listener); } catch (RemoteException e) { throw new SipException("createSipSession()", e); } diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java index 88bfba9..6d5cb3c 100644 --- a/voip/java/android/net/sip/SipProfile.java +++ b/voip/java/android/net/sip/SipProfile.java @@ -48,7 +48,6 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { private boolean mAutoRegistration = true; private transient int mCallingUid = 0; - /** @hide */ public static final Parcelable.Creator<SipProfile> CREATOR = new Parcelable.Creator<SipProfile>() { public SipProfile createFromParcel(Parcel in) { @@ -287,7 +286,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { mCallingUid = in.readInt(); } - /** @hide */ + @Override public void writeToParcel(Parcel out, int flags) { out.writeSerializable(mAddress); out.writeString(mProxyAddress); @@ -300,7 +299,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { out.writeInt(mCallingUid); } - /** @hide */ + @Override public int describeContents() { return 0; } diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java new file mode 100644 index 0000000..0cc7206 --- /dev/null +++ b/voip/java/android/net/sip/SipSession.java @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.sip; + +import android.os.RemoteException; +import android.util.Log; + +/** + * A SIP session that is associated with a SIP dialog or a standalone + * transaction not within a dialog. + * @hide + */ +public final class SipSession { + private static final String TAG = "SipSession"; + + /** + * Defines {@link SipSession} states. + * @hide + */ + public static class State { + /** When session is ready to initiate a call or transaction. */ + public static final int READY_TO_CALL = 0; + + /** When the registration request is sent out. */ + public static final int REGISTERING = 1; + + /** When the unregistration request is sent out. */ + public static final int DEREGISTERING = 2; + + /** When an INVITE request is received. */ + public static final int INCOMING_CALL = 3; + + /** When an OK response is sent for the INVITE request received. */ + public static final int INCOMING_CALL_ANSWERING = 4; + + /** When an INVITE request is sent. */ + public static final int OUTGOING_CALL = 5; + + /** When a RINGING response is received for the INVITE request sent. */ + public static final int OUTGOING_CALL_RING_BACK = 6; + + /** When a CANCEL request is sent for the INVITE request sent. */ + public static final int OUTGOING_CALL_CANCELING = 7; + + /** When a call is established. */ + public static final int IN_CALL = 8; + + /** When an OPTIONS request is sent. */ + public static final int PINGING = 9; + + /** Not defined. */ + public static final int NOT_DEFINED = 101; + + /** + * Converts the state to string. + */ + public static String toString(int state) { + switch (state) { + case READY_TO_CALL: + return "READY_TO_CALL"; + case REGISTERING: + return "REGISTERING"; + case DEREGISTERING: + return "DEREGISTERING"; + case INCOMING_CALL: + return "INCOMING_CALL"; + case INCOMING_CALL_ANSWERING: + return "INCOMING_CALL_ANSWERING"; + case OUTGOING_CALL: + return "OUTGOING_CALL"; + case OUTGOING_CALL_RING_BACK: + return "OUTGOING_CALL_RING_BACK"; + case OUTGOING_CALL_CANCELING: + return "OUTGOING_CALL_CANCELING"; + case IN_CALL: + return "IN_CALL"; + case PINGING: + return "PINGING"; + default: + return "NOT_DEFINED"; + } + } + + private State() { + } + } + + /** + * Listener class that listens to {@link SipSession} events. + * @hide + */ + public static class Listener { + /** + * Called when an INVITE request is sent to initiate a new call. + * + * @param session the session object that carries out the transaction + */ + public void onCalling(SipSession session) { + } + + /** + * Called when an INVITE request is received. + * + * @param session the session object that carries out the transaction + * @param caller the SIP profile of the caller + * @param sessionDescription the caller's session description + */ + public void onRinging(SipSession session, SipProfile caller, + String sessionDescription) { + } + + /** + * Called when a RINGING response is received for the INVITE request sent + * + * @param session the session object that carries out the transaction + */ + public void onRingingBack(SipSession session) { + } + + /** + * Called when the session is established. + * + * @param session the session object that is associated with the dialog + * @param sessionDescription the peer's session description + */ + public void onCallEstablished(SipSession session, + String sessionDescription) { + } + + /** + * Called when the session is terminated. + * + * @param session the session object that is associated with the dialog + */ + public void onCallEnded(SipSession session) { + } + + /** + * Called when the peer is busy during session initialization. + * + * @param session the session object that carries out the transaction + */ + public void onCallBusy(SipSession session) { + } + + /** + * Called when an error occurs during session initialization and + * termination. + * + * @param session the session object that carries out the transaction + * @param errorCode error code defined in {@link SipErrorCode} + * @param errorMessage error message + */ + public void onError(SipSession session, int errorCode, + String errorMessage) { + } + + /** + * Called when an error occurs during session modification negotiation. + * + * @param session the session object that carries out the transaction + * @param errorCode error code defined in {@link SipErrorCode} + * @param errorMessage error message + */ + public void onCallChangeFailed(SipSession session, int errorCode, + String errorMessage) { + } + + /** + * Called when a registration request is sent. + * + * @param session the session object that carries out the transaction + */ + public void onRegistering(SipSession session) { + } + + /** + * Called when registration is successfully done. + * + * @param session the session object that carries out the transaction + * @param duration duration in second before the registration expires + */ + public void onRegistrationDone(SipSession session, int duration) { + } + + /** + * Called when the registration fails. + * + * @param session the session object that carries out the transaction + * @param errorCode error code defined in {@link SipErrorCode} + * @param errorMessage error message + */ + public void onRegistrationFailed(SipSession session, int errorCode, + String errorMessage) { + } + + /** + * Called when the registration gets timed out. + * + * @param session the session object that carries out the transaction + */ + public void onRegistrationTimeout(SipSession session) { + } + } + + private final ISipSession mSession; + private Listener mListener; + + SipSession(ISipSession realSession) { + mSession = realSession; + if (realSession != null) { + try { + realSession.setListener(createListener()); + } catch (RemoteException e) { + Log.e(TAG, "SipSession.setListener(): " + e); + } + } + } + + SipSession(ISipSession realSession, Listener listener) { + this(realSession); + setListener(listener); + } + + /** + * Gets the IP address of the local host on which this SIP session runs. + * + * @return the IP address of the local host + */ + public String getLocalIp() { + try { + return mSession.getLocalIp(); + } catch (RemoteException e) { + Log.e(TAG, "getLocalIp(): " + e); + return "127.0.0.1"; + } + } + + /** + * Gets the SIP profile that this session is associated with. + * + * @return the SIP profile that this session is associated with + */ + public SipProfile getLocalProfile() { + try { + return mSession.getLocalProfile(); + } catch (RemoteException e) { + Log.e(TAG, "getLocalProfile(): " + e); + return null; + } + } + + /** + * Gets the SIP profile that this session is connected to. Only available + * when the session is associated with a SIP dialog. + * + * @return the SIP profile that this session is connected to + */ + public SipProfile getPeerProfile() { + try { + return mSession.getPeerProfile(); + } catch (RemoteException e) { + Log.e(TAG, "getPeerProfile(): " + e); + return null; + } + } + + /** + * Gets the session state. The value returned must be one of the states in + * {@link SipSessionState}. + * + * @return the session state + */ + public int getState() { + try { + return mSession.getState(); + } catch (RemoteException e) { + Log.e(TAG, "getState(): " + e); + return State.NOT_DEFINED; + } + } + + /** + * Checks if the session is in a call. + * + * @return true if the session is in a call + */ + public boolean isInCall() { + try { + return mSession.isInCall(); + } catch (RemoteException e) { + Log.e(TAG, "isInCall(): " + e); + return false; + } + } + + /** + * Gets the call ID of the session. + * + * @return the call ID + */ + public String getCallId() { + try { + return mSession.getCallId(); + } catch (RemoteException e) { + Log.e(TAG, "getCallId(): " + e); + return null; + } + } + + + /** + * Sets the listener to listen to the session events. A {@code SipSession} + * can only hold one listener at a time. Subsequent calls to this method + * override the previous listener. + * + * @param listener to listen to the session events of this object + */ + public void setListener(Listener listener) { + mListener = listener; + } + + + /** + * Performs registration to the server specified by the associated local + * profile. The session listener is called back upon success or failure of + * registration. The method is only valid to call when the session state is + * in {@link SipSessionState#READY_TO_CALL}. + * + * @param duration duration in second before the registration expires + * @see Listener + */ + public void register(int duration) { + try { + mSession.register(duration); + } catch (RemoteException e) { + Log.e(TAG, "register(): " + e); + } + } + + /** + * Performs unregistration to the server specified by the associated local + * profile. Unregistration is technically the same as registration with zero + * expiration duration. The session listener is called back upon success or + * failure of unregistration. The method is only valid to call when the + * session state is in {@link SipSessionState#READY_TO_CALL}. + * + * @see Listener + */ + public void unregister() { + try { + mSession.unregister(); + } catch (RemoteException e) { + Log.e(TAG, "unregister(): " + e); + } + } + + /** + * Initiates a call to the specified profile. The session listener is called + * back upon defined session events. The method is only valid to call when + * the session state is in {@link SipSessionState#READY_TO_CALL}. + * + * @param callee the SIP profile to make the call to + * @param sessionDescription the session description of this call + * @param timeout the session will be timed out if the call is not + * established within {@code timeout} seconds. Default value (defined + * by SIP protocol) is used if {@code timeout} is zero or negative. + * @see Listener + */ + public void makeCall(SipProfile callee, String sessionDescription, + int timeout) { + try { + mSession.makeCall(callee, sessionDescription, timeout); + } catch (RemoteException e) { + Log.e(TAG, "makeCall(): " + e); + } + } + + /** + * Answers an incoming call with the specified session description. The + * method is only valid to call when the session state is in + * {@link SipSessionState#INCOMING_CALL}. + * + * @param sessionDescription the session description to answer this call + * @param timeout the session will be timed out if the call is not + * established within {@code timeout} seconds. Default value (defined + * by SIP protocol) is used if {@code timeout} is zero or negative. + */ + public void answerCall(String sessionDescription, int timeout) { + try { + mSession.answerCall(sessionDescription, timeout); + } catch (RemoteException e) { + Log.e(TAG, "answerCall(): " + e); + } + } + + /** + * Ends an established call, terminates an outgoing call or rejects an + * incoming call. The method is only valid to call when the session state is + * in {@link SipSessionState#IN_CALL}, + * {@link SipSessionState#INCOMING_CALL}, + * {@link SipSessionState#OUTGOING_CALL} or + * {@link SipSessionState#OUTGOING_CALL_RING_BACK}. + */ + public void endCall() { + try { + mSession.endCall(); + } catch (RemoteException e) { + Log.e(TAG, "endCall(): " + e); + } + } + + /** + * Changes the session description during a call. The method is only valid + * to call when the session state is in {@link SipSessionState#IN_CALL}. + * + * @param sessionDescription the new session description + * @param timeout the session will be timed out if the call is not + * established within {@code timeout} seconds. Default value (defined + * by SIP protocol) is used if {@code timeout} is zero or negative. + */ + public void changeCall(String sessionDescription, int timeout) { + try { + mSession.changeCall(sessionDescription, timeout); + } catch (RemoteException e) { + Log.e(TAG, "changeCall(): " + e); + } + } + + ISipSession getRealSession() { + return mSession; + } + + private ISipSessionListener createListener() { + return new ISipSessionListener.Stub() { + public void onCalling(ISipSession session) { + if (mListener != null) { + mListener.onCalling(SipSession.this); + } + } + + public void onRinging(ISipSession session, SipProfile caller, + String sessionDescription) { + if (mListener != null) { + mListener.onRinging(SipSession.this, caller, + sessionDescription); + } + } + + public void onRingingBack(ISipSession session) { + if (mListener != null) { + mListener.onRingingBack(SipSession.this); + } + } + + public void onCallEstablished(ISipSession session, + String sessionDescription) { + if (mListener != null) { + mListener.onCallEstablished(SipSession.this, + sessionDescription); + } + } + + public void onCallEnded(ISipSession session) { + if (mListener != null) { + mListener.onCallEnded(SipSession.this); + } + } + + public void onCallBusy(ISipSession session) { + if (mListener != null) { + mListener.onCallBusy(SipSession.this); + } + } + + public void onCallChangeFailed(ISipSession session, int errorCode, + String message) { + if (mListener != null) { + mListener.onCallChangeFailed(SipSession.this, errorCode, + message); + } + } + + public void onError(ISipSession session, int errorCode, String message) { + if (mListener != null) { + mListener.onError(SipSession.this, errorCode, message); + } + } + + public void onRegistering(ISipSession session) { + if (mListener != null) { + mListener.onRegistering(SipSession.this); + } + } + + public void onRegistrationDone(ISipSession session, int duration) { + if (mListener != null) { + mListener.onRegistrationDone(SipSession.this, duration); + } + } + + public void onRegistrationFailed(ISipSession session, int errorCode, + String message) { + if (mListener != null) { + mListener.onRegistrationFailed(SipSession.this, errorCode, + message); + } + } + + public void onRegistrationTimeout(ISipSession session) { + if (mListener != null) { + mListener.onRegistrationTimeout(SipSession.this); + } + } + }; + } +} diff --git a/voip/java/android/net/sip/SipSessionState.java b/voip/java/android/net/sip/SipSessionState.java deleted file mode 100644 index 31e9d3f..0000000 --- a/voip/java/android/net/sip/SipSessionState.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.sip; - -/** - * Defines {@link ISipSession} states. - * @hide - */ -public class SipSessionState { - /** When session is ready to initiate a call or transaction. */ - public static final int READY_TO_CALL = 0; - - /** When the registration request is sent out. */ - public static final int REGISTERING = 1; - - /** When the unregistration request is sent out. */ - public static final int DEREGISTERING = 2; - - /** When an INVITE request is received. */ - public static final int INCOMING_CALL = 3; - - /** When an OK response is sent for the INVITE request received. */ - public static final int INCOMING_CALL_ANSWERING = 4; - - /** When an INVITE request is sent. */ - public static final int OUTGOING_CALL = 5; - - /** When a RINGING response is received for the INVITE request sent. */ - public static final int OUTGOING_CALL_RING_BACK = 6; - - /** When a CANCEL request is sent for the INVITE request sent. */ - public static final int OUTGOING_CALL_CANCELING = 7; - - /** When a call is established. */ - public static final int IN_CALL = 8; - - /** Some error occurs when making a remote call to {@link ISipSession}. */ - public static final int REMOTE_ERROR = 9; - - /** When an OPTIONS request is sent. */ - public static final int PINGING = 10; - - /** Not defined. */ - public static final int NOT_DEFINED = 101; - - /** - * Converts the state to string. - */ - public static String toString(int state) { - switch (state) { - case READY_TO_CALL: - return "READY_TO_CALL"; - case REGISTERING: - return "REGISTERING"; - case DEREGISTERING: - return "DEREGISTERING"; - case INCOMING_CALL: - return "INCOMING_CALL"; - case INCOMING_CALL_ANSWERING: - return "INCOMING_CALL_ANSWERING"; - case OUTGOING_CALL: - return "OUTGOING_CALL"; - case OUTGOING_CALL_RING_BACK: - return "OUTGOING_CALL_RING_BACK"; - case OUTGOING_CALL_CANCELING: - return "OUTGOING_CALL_CANCELING"; - case IN_CALL: - return "IN_CALL"; - case REMOTE_ERROR: - return "REMOTE_ERROR"; - case PINGING: - return "PINGING"; - default: - return "NOT_DEFINED"; - } - } - - private SipSessionState() { - } -} |