summaryrefslogtreecommitdiff
path: root/voip/java/android/net/sip/SipAudioCall.java
diff options
context:
space:
mode:
Diffstat (limited to 'voip/java/android/net/sip/SipAudioCall.java')
-rw-r--r--voip/java/android/net/sip/SipAudioCall.java1143
1 files changed, 0 insertions, 1143 deletions
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
deleted file mode 100644
index ea943e969a39..000000000000
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ /dev/null
@@ -1,1143 +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.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.telephony.Rlog;
-import android.text.TextUtils;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager},
- * using {@link SipManager#makeAudioCall makeAudioCall()} and {@link SipManager#takeAudioCall
- * takeAudioCall()}.
- *
- * <p class="note"><strong>Note:</strong> Using this class require the
- * {@link android.Manifest.permission#INTERNET} and
- * {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link
- * #startAudio} requires the
- * {@link android.Manifest.permission#RECORD_AUDIO},
- * {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and
- * {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode
- * setSpeakerMode()} requires the
- * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For more information about using SIP, read the
- * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
- * developer guide.</p>
- * </div>
- */
-public class SipAudioCall {
- private static final String LOG_TAG = SipAudioCall.class.getSimpleName();
- private static final boolean DBG = true;
- 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 static final int TRANSFER_TIMEOUT = 15; // in seconds
-
- /** Listener for events relating to a SIP call, such as when a call is being
- * recieved ("on ringing") or a call is outgoing ("on calling").
- * <p>Many of these events are also received by {@link SipSession.Listener}.</p>
- */
- public static class Listener {
- /**
- * Called when the call object is ready to make another call.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that is ready to make another 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 #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCalling(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when a new call comes in.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- * @param caller the SIP profile of the caller
- */
- public void onRinging(SipAudioCall call, SipProfile caller) {
- onChanged(call);
- }
-
- /**
- * Called when a RINGING response is received for the INVITE request
- * sent. The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onRingingBack(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when the session is established.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCallEstablished(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when the session is terminated.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCallEnded(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when the peer is busy during session initialization.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCallBusy(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when the call is on hold.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCallHeld(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * 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
- */
- 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 SipSession mTransferringSession;
-
- 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 WifiManager mWm;
- private WifiManager.WifiLock mWifiHighPerfLock;
-
- private int mErrorCode = SipErrorCode.NO_ERROR;
- private String mErrorMessage;
-
- /**
- * Creates a call object with the local SIP profile.
- * @param context the context for accessing system services such as
- * ringtone, audio, WIFI etc
- */
- public SipAudioCall(Context context, SipProfile localProfile) {
- mContext = context;
- mLocalProfile = localProfile;
- mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- }
-
- /**
- * Sets the listener to listen to the audio call events. The method calls
- * {@link #setListener setListener(listener, false)}.
- *
- * @param listener to listen to the audio call events of this object
- * @see #setListener(Listener, boolean)
- */
- public void setListener(SipAudioCall.Listener listener) {
- setListener(listener, false);
- }
-
- /**
- * 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.
- *
- * @param listener to listen to the audio call events of this object
- * @param callbackImmediately set to true if the caller wants to be called
- * back immediately on the current state
- */
- 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) {
- loge("setListener()", t);
- }
- }
-
- /**
- * Checks if the call is established.
- *
- * @return true if the call is established
- */
- public boolean isInCall() {
- synchronized (this) {
- return mInCall;
- }
- }
-
- /**
- * Checks if the call is on hold.
- *
- * @return true if the call is on hold
- */
- public boolean isOnHold() {
- synchronized (this) {
- return mHold;
- }
- }
-
- /**
- * Closes this object. This object is not usable after being closed.
- */
- public void close() {
- 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;
- }
- }
-
- /**
- * Gets the local SIP profile.
- *
- * @return the local SIP profile
- */
- public SipProfile getLocalProfile() {
- synchronized (this) {
- return mLocalProfile;
- }
- }
-
- /**
- * Gets the peer's SIP profile.
- *
- * @return the peer's SIP profile
- */
- public SipProfile getPeerProfile() {
- synchronized (this) {
- 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 int getState() {
- synchronized (this) {
- 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 SipSession getSipSession() {
- synchronized (this) {
- return mSipSession;
- }
- }
-
- 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();
- }
-
- 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 void onError(int errorCode, 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);
- }
- }
- }
-
- /**
- * 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 or VOIP API is not supported by the device
- * @see SipManager#isVoipSupported
- */
- public void attachCall(SipSession session, String sessionDescription)
- throws SipException {
- 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);
- }
- }
- }
-
- /**
- * 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.
- *
- * @param peerProfile the SIP profile to make the call to
- * @param sipSession the {@link SipSession} for carrying out the call
- * @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 or VOIP API is not supported by the device
- * @see SipManager#isVoipSupported
- */
- public void makeCall(SipProfile peerProfile, SipSession sipSession,
- int timeout) throws SipException {
- 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);
- }
- }
- }
-
- /**
- * Ends a call.
- * @throws SipException if the SIP service fails to end the call
- */
- public void endCall() throws SipException {
- 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();
- }
- }
-
- /**
- * 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.
- *
- * @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
- */
- public void holdCall(int timeout) throws SipException {
- 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();
- }
- }
-
- /**
- * 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.
- *
- * @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
- */
- public void answerCall(int timeout) throws SipException {
- 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);
- }
- }
- }
-
- /**
- * 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.
- *
- * @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
- */
- public void continueCall(int timeout) throws SipException {
- if (DBG) log("continueCall: mSipSession" + mSipSession + " timeout=" + timeout);
- synchronized (this) {
- if (!mHold) return;
- mSipSession.changeCall(createContinueOffer().encode(), timeout);
- mHold = false;
- setAudioGroupMode();
- }
- }
-
- 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;
- }
-
- private SimpleSessionDescription createAnswer(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 createHoldOffer() {
- SimpleSessionDescription offer = createContinueOffer();
- offer.setAttribute("sendonly", "");
- if (DBG) log("createHoldOffer: offer=" + offer);
- return offer;
- }
-
- 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 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();
- }
- }
-
- private void releaseWifiHighPerfLock() {
- if (mWifiHighPerfLock != null) {
- if (DBG) log("releaseWifiHighPerfLock:");
- mWifiHighPerfLock.release();
- mWifiHighPerfLock = null;
- }
- }
-
- private boolean isWifiOn() {
- return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
- }
-
- /** Toggles mute. */
- public void toggleMute() {
- synchronized (this) {
- mMuted = !mMuted;
- setAudioGroupMode();
- }
- }
-
- /**
- * Checks if the call is muted.
- *
- * @return true if the call is muted
- */
- public boolean isMuted() {
- synchronized (this) {
- return mMuted;
- }
- }
-
- /**
- * Puts the device to speaker mode.
- * <p class="note"><strong>Note:</strong> Requires the
- * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
- *
- * @param speakerMode set true to enable speaker mode; false to disable
- */
- public void setSpeakerMode(boolean speakerMode) {
- synchronized (this) {
- ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
- .setSpeakerphoneOn(speakerMode);
- setAudioGroupMode();
- }
- }
-
- private boolean isSpeakerOn() {
- return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
- .isSpeakerphoneOn();
- }
-
- /**
- * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
- * 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. Value 0 to 15 (inclusive) are valid
- * inputs.
- */
- public void sendDtmf(int code) {
- sendDtmf(code, null);
- }
-
- /**
- * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
- * 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. Value 0 to 15 (inclusive) are valid
- * inputs.
- * @param result the result message to send when done
- */
- public void sendDtmf(int code, Message result) {
- 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();
- }
- }
-
- /**
- * 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.
- *
- * @return the {@link AudioStream} object or null if the RTP stream has not
- * yet been set up
- * @hide
- */
- public AudioStream getAudioStream() {
- synchronized (this) {
- return mAudioStream;
- }
- }
-
- /**
- * 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.
- *
- * @return the {@link AudioGroup} object or null if the RTP stream has not
- * yet been set up
- * @see #getAudioStream
- * @hide
- */
- public AudioGroup getAudioGroup() {
- synchronized (this) {
- if (mAudioGroup != null) return mAudioGroup;
- return ((mAudioStream == null) ? null : mAudioStream.getGroup());
- }
- }
-
- /**
- * 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'.
- *
- * @see #getAudioStream
- * @hide
- */
- public void setAudioGroup(AudioGroup group) {
- synchronized (this) {
- if (DBG) log("setAudioGroup: group=" + group);
- if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
- mAudioStream.join(group);
- }
- mAudioGroup = group;
- }
- }
-
- /**
- * Starts the audio for the established call. This method should be called
- * after {@link Listener#onCallEstablished} is called.
- * <p class="note"><strong>Note:</strong> Requires the
- * {@link android.Manifest.permission#RECORD_AUDIO},
- * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
- * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p>
- */
- public void startAudio() {
- 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() throws UnknownHostException {
- 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();
- }
-
- // set audio group mode based on current audio configuration
- 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);
- }
- }
- }
-
- 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 String getLocalIp() {
- return mSipSession.getLocalIp();
- }
-
- private void throwSipException(Throwable throwable) throws SipException {
- if (throwable instanceof SipException) {
- throw (SipException) throwable;
- } else {
- throw new SipException("", throwable);
- }
- }
-
- private void log(String s) {
- Rlog.d(LOG_TAG, s);
- }
-
- private void loge(String s) {
- Rlog.e(LOG_TAG, s);
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(LOG_TAG, s, t);
- }
-}