Methods Summary |
---|
public void | answerCall(int timeout)Answers a call. The attempt will be timed out if the call is not
established within {@code timeout} seconds and
{@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
will be called.
if (DBG) log("answerCall: mSipSession" + mSipSession + " timeout=" + timeout);
synchronized (this) {
if (mSipSession == null) {
throw new SipException("No call to answer");
}
try {
mAudioStream = new AudioStream(InetAddress.getByName(
getLocalIp()));
mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
} catch (IOException e) {
loge("answerCall:", e);
throw new SipException("answerCall()", e);
}
}
|
public void | attachCall(SipSession session, java.lang.String sessionDescription)Attaches an incoming call to this call object.
if (!SipManager.isVoipSupported(mContext)) {
throw new SipException("VOIP API is not supported");
}
synchronized (this) {
mSipSession = session;
mPeerSd = sessionDescription;
if (DBG) log("attachCall(): " + mPeerSd);
try {
session.setListener(createListener());
} catch (Throwable e) {
loge("attachCall()", e);
throwSipException(e);
}
}
|
public void | close()Closes this object. This object is not usable after being closed.
close(true);
|
private synchronized void | close(boolean closeRtp)
if (closeRtp) stopCall(RELEASE_SOCKET);
mInCall = false;
mHold = false;
mSessionId = System.currentTimeMillis();
mErrorCode = SipErrorCode.NO_ERROR;
mErrorMessage = null;
if (mSipSession != null) {
mSipSession.setListener(null);
mSipSession = null;
}
|
public void | continueCall(int timeout)Continues a call that's on hold. When succeeds,
{@link Listener#onCallEstablished} is called. The attempt will be timed
out if the call is not established within {@code timeout} seconds and
{@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
will be called.
if (DBG) log("continueCall: mSipSession" + mSipSession + " timeout=" + timeout);
synchronized (this) {
if (!mHold) return;
mSipSession.changeCall(createContinueOffer().encode(), timeout);
mHold = false;
setAudioGroupMode();
}
|
private SimpleSessionDescription | createAnswer(java.lang.String offerSd)
if (TextUtils.isEmpty(offerSd)) return createOffer();
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) {
loge("createAnswer: no suitable codes");
throw new IllegalStateException("Reject SDP: no suitable codecs");
}
if (DBG) log("createAnswer: answer=" + answer);
return answer;
|
private SimpleSessionDescription | createContinueOffer()
if (DBG) log("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 SimpleSessionDescription | createHoldOffer()
SimpleSessionDescription offer = createContinueOffer();
offer.setAttribute("sendonly", "");
if (DBG) log("createHoldOffer: offer=" + offer);
return offer;
|
private SipSession.Listener | createListener()
return new SipSession.Listener() {
@Override
public void onCalling(SipSession session) {
if (DBG) log("onCalling: session=" + session);
Listener listener = mListener;
if (listener != null) {
try {
listener.onCalling(SipAudioCall.this);
} catch (Throwable t) {
loge("onCalling():", t);
}
}
}
@Override
public void onRingingBack(SipSession session) {
if (DBG) log("onRingingBackk: " + session);
Listener listener = mListener;
if (listener != null) {
try {
listener.onRingingBack(SipAudioCall.this);
} catch (Throwable t) {
loge("onRingingBack():", t);
}
}
}
@Override
public void onRinging(SipSession session,
SipProfile peerProfile, String sessionDescription) {
// this callback is triggered only for reinvite.
synchronized (SipAudioCall.this) {
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) {
loge("onRinging():", e);
session.endCall();
}
}
}
@Override
public void onCallEstablished(SipSession session,
String sessionDescription) {
mPeerSd = sessionDescription;
if (DBG) log("onCallEstablished(): " + mPeerSd);
// TODO: how to notify the UI that the remote party is changed
if ((mTransferringSession != null)
&& (session == mTransferringSession)) {
transferToNewSession();
return;
}
Listener listener = mListener;
if (listener != null) {
try {
if (mHold) {
listener.onCallHeld(SipAudioCall.this);
} else {
listener.onCallEstablished(SipAudioCall.this);
}
} catch (Throwable t) {
loge("onCallEstablished(): ", t);
}
}
}
@Override
public void onCallEnded(SipSession session) {
if (DBG) log("onCallEnded: " + session + " mSipSession:" + mSipSession);
// reset the trasnferring session if it is the one.
if (session == mTransferringSession) {
mTransferringSession = null;
return;
}
// or ignore the event if the original session is being
// transferred to the new one.
if ((mTransferringSession != null) ||
(session != mSipSession)) return;
Listener listener = mListener;
if (listener != null) {
try {
listener.onCallEnded(SipAudioCall.this);
} catch (Throwable t) {
loge("onCallEnded(): ", t);
}
}
close();
}
@Override
public void onCallBusy(SipSession session) {
if (DBG) log("onCallBusy: " + session);
Listener listener = mListener;
if (listener != null) {
try {
listener.onCallBusy(SipAudioCall.this);
} catch (Throwable t) {
loge("onCallBusy(): ", t);
}
}
close(false);
}
@Override
public void onCallChangeFailed(SipSession session, int errorCode,
String message) {
if (DBG) log("onCallChangedFailed: " + message);
mErrorCode = errorCode;
mErrorMessage = message;
Listener listener = mListener;
if (listener != null) {
try {
listener.onError(SipAudioCall.this, mErrorCode,
message);
} catch (Throwable t) {
loge("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
}
@Override
public void onCallTransferring(SipSession newSession,
String sessionDescription) {
if (DBG) log("onCallTransferring: mSipSession="
+ mSipSession + " newSession=" + newSession);
mTransferringSession = newSession;
try {
if (sessionDescription == null) {
newSession.makeCall(newSession.getPeerProfile(),
createOffer().encode(), TRANSFER_TIMEOUT);
} else {
String answer = createAnswer(sessionDescription).encode();
newSession.answerCall(answer, SESSION_TIMEOUT);
}
} catch (Throwable e) {
loge("onCallTransferring()", e);
newSession.endCall();
}
}
};
|
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");
if (DBG) log("createOffer: offer=" + offer);
return offer;
|
public void | endCall()Ends a call.
if (DBG) log("endCall: mSipSession" + mSipSession);
synchronized (this) {
stopCall(RELEASE_SOCKET);
mInCall = false;
// perform the above local ops first and then network op
if (mSipSession != null) mSipSession.endCall();
}
|
public android.net.rtp.AudioGroup | getAudioGroup()Gets the {@link AudioGroup} object which the {@link AudioStream} object
joins. The group object may not exist before the call is established.
Also, the {@code AudioStream} may change its group during a call (e.g.,
after the call is held/un-held). Finally, the {@code AudioGroup} object
returned by this method is undefined after the call ends or the
{@link #close} method is called. If a group object is set by
{@link #setAudioGroup(AudioGroup)}, then this method returns that object.
synchronized (this) {
if (mAudioGroup != null) return mAudioGroup;
return ((mAudioStream == null) ? null : mAudioStream.getGroup());
}
|
public android.net.rtp.AudioStream | getAudioStream()Gets the {@link AudioStream} object used in this call. The object
represents the RTP stream that carries the audio data to and from the
peer. The object may not be created before the call is established. And
it is undefined after the call ends or the {@link #close} method is
called.
synchronized (this) {
return mAudioStream;
}
|
private java.lang.String | getLocalIp()
return mSipSession.getLocalIp();
|
public SipProfile | getLocalProfile()Gets the local SIP profile.
synchronized (this) {
return mLocalProfile;
}
|
public SipProfile | getPeerProfile()Gets the peer's SIP profile.
synchronized (this) {
return (mSipSession == null) ? null : mSipSession.getPeerProfile();
}
|
public SipSession | getSipSession()Gets the {@link SipSession} that carries this call.
synchronized (this) {
return mSipSession;
}
|
public int | getState()Gets the state of the {@link SipSession} that carries this call.
The value returned must be one of the states in {@link SipSession.State}.
synchronized (this) {
if (mSipSession == null) return SipSession.State.READY_TO_CALL;
return mSipSession.getState();
}
|
private void | grabWifiHighPerfLock()
if (mWifiHighPerfLock == null) {
if (DBG) log("grabWifiHighPerfLock:");
mWifiHighPerfLock = ((WifiManager)
mContext.getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOG_TAG);
mWifiHighPerfLock.acquire();
}
|
public void | holdCall(int timeout)Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is
called. The attempt will be timed out if the call is not established
within {@code timeout} seconds and
{@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
will be called.
if (DBG) log("holdCall: mSipSession" + mSipSession + " timeout=" + timeout);
synchronized (this) {
if (mHold) return;
if (mSipSession == null) {
loge("holdCall:");
throw new SipException("Not in a call to hold call");
}
mSipSession.changeCall(createHoldOffer().encode(), timeout);
mHold = true;
setAudioGroupMode();
}
|
public boolean | isInCall()Checks if the call is established.
synchronized (this) {
return mInCall;
}
|
public boolean | isMuted()Checks if the call is muted.
synchronized (this) {
return mMuted;
}
|
public boolean | isOnHold()Checks if the call is on hold.
synchronized (this) {
return mHold;
}
|
private boolean | isSpeakerOn()
return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
.isSpeakerphoneOn();
|
private boolean | isWifiOn()
return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
|
private void | log(java.lang.String s)
Rlog.d(LOG_TAG, s);
|
private void | loge(java.lang.String s)
Rlog.e(LOG_TAG, s);
|
private void | loge(java.lang.String s, java.lang.Throwable t)
Rlog.e(LOG_TAG, s, t);
|
public void | makeCall(SipProfile peerProfile, SipSession sipSession, int timeout)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 {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
will be called.
if (DBG) log("makeCall: " + peerProfile + " session=" + sipSession + " timeout=" + timeout);
if (!SipManager.isVoipSupported(mContext)) {
throw new SipException("VOIP API is not supported");
}
synchronized (this) {
mSipSession = sipSession;
try {
mAudioStream = new AudioStream(InetAddress.getByName(
getLocalIp()));
sipSession.setListener(createListener());
sipSession.makeCall(peerProfile, createOffer().encode(),
timeout);
} catch (IOException e) {
loge("makeCall:", e);
throw new SipException("makeCall()", e);
}
}
|
private void | onError(int errorCode, java.lang.String message)
if (DBG) log("onError: "
+ SipErrorCode.toString(errorCode) + ": " + message);
mErrorCode = errorCode;
mErrorMessage = message;
Listener listener = mListener;
if (listener != null) {
try {
listener.onError(this, errorCode, message);
} catch (Throwable t) {
loge("onError():", t);
}
}
synchronized (this) {
if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
|| !isInCall()) {
close(true);
}
}
|
private void | releaseWifiHighPerfLock()
if (mWifiHighPerfLock != null) {
if (DBG) log("releaseWifiHighPerfLock:");
mWifiHighPerfLock.release();
mWifiHighPerfLock = null;
}
|
public void | sendDtmf(int code)Sends a DTMF code. According to RFC 2883,
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.
sendDtmf(code, null);
|
public void | sendDtmf(int code, android.os.Message result)Sends a DTMF code. According to RFC 2883,
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.
synchronized (this) {
AudioGroup audioGroup = getAudioGroup();
if ((audioGroup != null) && (mSipSession != null)
&& (SipSession.State.IN_CALL == getState())) {
if (DBG) log("sendDtmf: code=" + code + " result=" + result);
audioGroup.sendDtmf(code);
}
if (result != null) result.sendToTarget();
}
|
public void | setAudioGroup(android.net.rtp.AudioGroup group)Sets the {@link AudioGroup} object which the {@link AudioStream} object
joins. If {@code audioGroup} is null, then the {@code AudioGroup} object
will be dynamically created when needed. Note that the mode of the
{@code AudioGroup} is not changed according to the audio settings (i.e.,
hold, mute, speaker phone) of this object. This is mainly used to merge
multiple {@code SipAudioCall} objects to form a conference call. The
settings of the first object (that merges others) override others'.
synchronized (this) {
if (DBG) log("setAudioGroup: group=" + group);
if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
mAudioStream.join(group);
}
mAudioGroup = group;
}
|
private void | setAudioGroupMode()
AudioGroup audioGroup = getAudioGroup();
if (DBG) log("setAudioGroupMode: audioGroup=" + audioGroup);
if (audioGroup != null) {
if (mHold) {
audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
} else if (mMuted) {
audioGroup.setMode(AudioGroup.MODE_MUTED);
} else if (isSpeakerOn()) {
audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
} else {
audioGroup.setMode(AudioGroup.MODE_NORMAL);
}
}
|
public void | setListener(android.net.sip.SipAudioCall$Listener listener)Sets the listener to listen to the audio call events. The method calls
{@link #setListener setListener(listener, false)}.
setListener(listener, false);
|
public void | setListener(android.net.sip.SipAudioCall$Listener listener, boolean callbackImmediately)Sets the listener to listen to the audio call events. A
{@link SipAudioCall} can only hold one listener at a time. Subsequent
calls to this method override the previous listener.
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) {
loge("setListener()", t);
}
|
public void | setSpeakerMode(boolean speakerMode)Puts the device to speaker mode.
Note: Requires the
{@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.
synchronized (this) {
((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
.setSpeakerphoneOn(speakerMode);
setAudioGroupMode();
}
|
public void | startAudio()Starts the audio for the established call. This method should be called
after {@link Listener#onCallEstablished} is called.
Note: Requires the
{@link android.Manifest.permission#RECORD_AUDIO},
{@link android.Manifest.permission#ACCESS_WIFI_STATE} and
{@link android.Manifest.permission#WAKE_LOCK} permissions.
try {
startAudioInternal();
} catch (UnknownHostException e) {
onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
} catch (Throwable e) {
onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
}
|
private synchronized void | startAudioInternal()
if (DBG) loge("startAudioInternal: mPeerSd=" + mPeerSd);
if (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 (isWifiOn()) grabWifiHighPerfLock();
// AudioGroup logic:
AudioGroup audioGroup = getAudioGroup();
if (mHold) {
// 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);
}
setAudioGroupMode();
|
private void | stopCall(boolean releaseSocket)
if (DBG) log("stopCall: releaseSocket=" + releaseSocket);
releaseWifiHighPerfLock();
if (mAudioStream != null) {
mAudioStream.join(null);
if (releaseSocket) {
mAudioStream.release();
mAudioStream = null;
}
}
|
private void | throwSipException(java.lang.Throwable throwable)
if (throwable instanceof SipException) {
throw (SipException) throwable;
} else {
throw new SipException("", throwable);
}
|
public void | toggleMute()Toggles mute.
synchronized (this) {
mMuted = !mMuted;
setAudioGroupMode();
}
|
private synchronized void | transferToNewSession()
if (mTransferringSession == null) return;
SipSession origin = mSipSession;
mSipSession = mTransferringSession;
mTransferringSession = null;
// stop the replaced call.
if (mAudioStream != null) {
mAudioStream.join(null);
} else {
try {
mAudioStream = new AudioStream(InetAddress.getByName(
getLocalIp()));
} catch (Throwable t) {
loge("transferToNewSession():", t);
}
}
if (origin != null) origin.endCall();
startAudio();
|