diff options
Diffstat (limited to 'voip/java/android/net/sip/SipAudioCall.java')
-rw-r--r-- | voip/java/android/net/sip/SipAudioCall.java | 1143 |
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); - } -} |