739 lines
25 KiB
Java
739 lines
25 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|