Refactoring SIP classes to get ready for API review.
+ replace SipAudioCall and its Listener interfaces with real implementations, + remove SipAudioCallImpl.java, most of it is has become part of SipAudioCall, + add SipSession and its Listener classes to wrap ISipSession and ISipSessionListener, + move SipSessionState to SipSession.State, + make SipManager keep context and remove the context argument from many methods of its, + rename SipManager.getInstance() to newInstance(), + rename constant names for action strings and extra keys to follow conventions, + set thread names for debugging purpose. Change-Id: Ie1790dc0e8f49c06c7fc80d33fec0f673a9c3044
This commit is contained in:
@ -30,8 +30,8 @@ import android.net.sip.ISipSessionListener;
|
|||||||
import android.net.sip.SipErrorCode;
|
import android.net.sip.SipErrorCode;
|
||||||
import android.net.sip.SipManager;
|
import android.net.sip.SipManager;
|
||||||
import android.net.sip.SipProfile;
|
import android.net.sip.SipProfile;
|
||||||
|
import android.net.sip.SipSession;
|
||||||
import android.net.sip.SipSessionAdapter;
|
import android.net.sip.SipSessionAdapter;
|
||||||
import android.net.sip.SipSessionState;
|
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -143,7 +143,7 @@ public final class SipService extends ISipService.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openToReceiveCalls(SipProfile localProfile) {
|
private void openToReceiveCalls(SipProfile localProfile) {
|
||||||
open3(localProfile, SipManager.SIP_INCOMING_CALL_ACTION, null);
|
open3(localProfile, SipManager.ACTION_SIP_INCOMING_CALL, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void open3(SipProfile localProfile,
|
public synchronized void open3(SipProfile localProfile,
|
||||||
@ -255,15 +255,15 @@ public final class SipService extends ISipService.Stub {
|
|||||||
|
|
||||||
private void notifyProfileAdded(SipProfile localProfile) {
|
private void notifyProfileAdded(SipProfile localProfile) {
|
||||||
if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
|
if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
|
||||||
Intent intent = new Intent(SipManager.SIP_ADD_PHONE_ACTION);
|
Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
|
||||||
intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
|
intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
|
||||||
mContext.sendBroadcast(intent);
|
mContext.sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyProfileRemoved(SipProfile localProfile) {
|
private void notifyProfileRemoved(SipProfile localProfile) {
|
||||||
if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
|
if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
|
||||||
Intent intent = new Intent(SipManager.SIP_REMOVE_PHONE_ACTION);
|
Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE);
|
||||||
intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
|
intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
|
||||||
mContext.sendBroadcast(intent);
|
mContext.sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,8 +474,8 @@ public final class SipService extends ISipService.Stub {
|
|||||||
// send out incoming call broadcast
|
// send out incoming call broadcast
|
||||||
addPendingSession(session);
|
addPendingSession(session);
|
||||||
Intent intent = SipManager.createIncomingCallBroadcast(
|
Intent intent = SipManager.createIncomingCallBroadcast(
|
||||||
mIncomingCallBroadcastAction, session.getCallId(),
|
session.getCallId(), sessionDescription)
|
||||||
sessionDescription);
|
.setAction(mIncomingCallBroadcastAction);
|
||||||
if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
|
if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
|
||||||
+ caller.getUri() + ": " + session.getCallId()
|
+ caller.getUri() + ": " + session.getCallId()
|
||||||
+ " " + mIncomingCallBroadcastAction);
|
+ " " + mIncomingCallBroadcastAction);
|
||||||
@ -613,10 +613,10 @@ public final class SipService extends ISipService.Stub {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
int state = (mSession == null)
|
int state = (mSession == null)
|
||||||
? SipSessionState.READY_TO_CALL
|
? SipSession.State.READY_TO_CALL
|
||||||
: mSession.getState();
|
: mSession.getState();
|
||||||
if ((state == SipSessionState.REGISTERING)
|
if ((state == SipSession.State.REGISTERING)
|
||||||
|| (state == SipSessionState.DEREGISTERING)) {
|
|| (state == SipSession.State.DEREGISTERING)) {
|
||||||
mProxy.onRegistering(mSession);
|
mProxy.onRegistering(mSession);
|
||||||
} else if (mRegistered) {
|
} else if (mRegistered) {
|
||||||
int duration = (int)
|
int duration = (int)
|
||||||
@ -1138,7 +1138,8 @@ public final class SipService extends ISipService.Stub {
|
|||||||
event.mTriggerTime += event.mPeriod;
|
event.mTriggerTime += event.mPeriod;
|
||||||
|
|
||||||
// run the callback in a new thread to prevent deadlock
|
// run the callback in a new thread to prevent deadlock
|
||||||
new Thread(event.mCallback).start();
|
new Thread(event.mCallback, "SipServiceTimerCallbackThread")
|
||||||
|
.start();
|
||||||
}
|
}
|
||||||
if (DEBUG_TIMER) {
|
if (DEBUG_TIMER) {
|
||||||
Log.d(TAG, "after timeout execution");
|
Log.d(TAG, "after timeout execution");
|
||||||
|
@ -28,8 +28,8 @@ import android.net.sip.ISipSessionListener;
|
|||||||
import android.net.sip.SessionDescription;
|
import android.net.sip.SessionDescription;
|
||||||
import android.net.sip.SipErrorCode;
|
import android.net.sip.SipErrorCode;
|
||||||
import android.net.sip.SipProfile;
|
import android.net.sip.SipProfile;
|
||||||
|
import android.net.sip.SipSession;
|
||||||
import android.net.sip.SipSessionAdapter;
|
import android.net.sip.SipSessionAdapter;
|
||||||
import android.net.sip.SipSessionState;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
reset(localIp);
|
reset(localIp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset(String localIp) throws SipException, IOException {
|
synchronized void reset(String localIp) throws SipException, IOException {
|
||||||
mLocalIp = localIp;
|
mLocalIp = localIp;
|
||||||
if (localIp == null) return;
|
if (localIp == null) return;
|
||||||
|
|
||||||
@ -301,7 +301,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
boolean processed = (session != null) && session.process(event);
|
boolean processed = (session != null) && session.process(event);
|
||||||
if (isLoggable && processed) {
|
if (isLoggable && processed) {
|
||||||
Log.d(TAG, "new state after: "
|
Log.d(TAG, "new state after: "
|
||||||
+ SipSessionState.toString(session.mState));
|
+ SipSession.State.toString(session.mState));
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.w(TAG, "event process error: " + event, e);
|
Log.w(TAG, "event process error: " + event, e);
|
||||||
@ -332,7 +332,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
|
|
||||||
public boolean process(EventObject evt) throws SipException {
|
public boolean process(EventObject evt) throws SipException {
|
||||||
if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": "
|
if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": "
|
||||||
+ SipSessionState.toString(mState) + ": processing "
|
+ SipSession.State.toString(mState) + ": processing "
|
||||||
+ log(evt));
|
+ log(evt));
|
||||||
if (isRequestEvent(Request.INVITE, evt)) {
|
if (isRequestEvent(Request.INVITE, evt)) {
|
||||||
RequestEvent event = (RequestEvent) evt;
|
RequestEvent event = (RequestEvent) evt;
|
||||||
@ -342,7 +342,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
newSession.mDialog = newSession.mServerTransaction.getDialog();
|
newSession.mDialog = newSession.mServerTransaction.getDialog();
|
||||||
newSession.mInviteReceived = event;
|
newSession.mInviteReceived = event;
|
||||||
newSession.mPeerProfile = createPeerProfile(event.getRequest());
|
newSession.mPeerProfile = createPeerProfile(event.getRequest());
|
||||||
newSession.mState = SipSessionState.INCOMING_CALL;
|
newSession.mState = SipSession.State.INCOMING_CALL;
|
||||||
newSession.mPeerSessionDescription =
|
newSession.mPeerSessionDescription =
|
||||||
extractContent(event.getRequest());
|
extractContent(event.getRequest());
|
||||||
addSipSession(newSession);
|
addSipSession(newSession);
|
||||||
@ -361,7 +361,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
class SipSessionImpl extends ISipSession.Stub {
|
class SipSessionImpl extends ISipSession.Stub {
|
||||||
SipProfile mPeerProfile;
|
SipProfile mPeerProfile;
|
||||||
SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
|
SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
|
||||||
int mState = SipSessionState.READY_TO_CALL;
|
int mState = SipSession.State.READY_TO_CALL;
|
||||||
RequestEvent mInviteReceived;
|
RequestEvent mInviteReceived;
|
||||||
Dialog mDialog;
|
Dialog mDialog;
|
||||||
ServerTransaction mServerTransaction;
|
ServerTransaction mServerTransaction;
|
||||||
@ -381,7 +381,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
sleep(timeout);
|
sleep(timeout);
|
||||||
if (mRunning) timeout();
|
if (mRunning) timeout();
|
||||||
}
|
}
|
||||||
}).start();
|
}, "SipSessionTimerThread").start();
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void cancel() {
|
synchronized void cancel() {
|
||||||
@ -416,7 +416,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
mInCall = false;
|
mInCall = false;
|
||||||
removeSipSession(this);
|
removeSipSession(this);
|
||||||
mPeerProfile = null;
|
mPeerProfile = null;
|
||||||
mState = SipSessionState.READY_TO_CALL;
|
mState = SipSession.State.READY_TO_CALL;
|
||||||
mInviteReceived = null;
|
mInviteReceived = null;
|
||||||
mDialog = null;
|
mDialog = null;
|
||||||
mServerTransaction = null;
|
mServerTransaction = null;
|
||||||
@ -473,7 +473,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
onError(e);
|
onError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}, "SipSessionAsyncCmdThread").start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void makeCall(SipProfile peerProfile, String sessionDescription,
|
public void makeCall(SipProfile peerProfile, String sessionDescription,
|
||||||
@ -523,10 +523,10 @@ class SipSessionGroup implements SipListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void sendKeepAlive() {
|
public void sendKeepAlive() {
|
||||||
mState = SipSessionState.PINGING;
|
mState = SipSession.State.PINGING;
|
||||||
try {
|
try {
|
||||||
processCommand(new OptionsCommand());
|
processCommand(new OptionsCommand());
|
||||||
while (SipSessionState.PINGING == mState) {
|
while (SipSession.State.PINGING == mState) {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
}
|
}
|
||||||
} catch (SipException e) {
|
} catch (SipException e) {
|
||||||
@ -553,7 +553,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
try {
|
try {
|
||||||
String s = super.toString();
|
String s = super.toString();
|
||||||
return s.substring(s.indexOf("@")) + ":"
|
return s.substring(s.indexOf("@")) + ":"
|
||||||
+ SipSessionState.toString(mState);
|
+ SipSession.State.toString(mState);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
return super.toString();
|
return super.toString();
|
||||||
}
|
}
|
||||||
@ -561,7 +561,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
|
|
||||||
public boolean process(EventObject evt) throws SipException {
|
public boolean process(EventObject evt) throws SipException {
|
||||||
if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": "
|
if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": "
|
||||||
+ SipSessionState.toString(mState) + ": processing "
|
+ SipSession.State.toString(mState) + ": processing "
|
||||||
+ log(evt));
|
+ log(evt));
|
||||||
synchronized (SipSessionGroup.this) {
|
synchronized (SipSessionGroup.this) {
|
||||||
if (isClosed()) return false;
|
if (isClosed()) return false;
|
||||||
@ -577,30 +577,30 @@ class SipSessionGroup implements SipListener {
|
|||||||
boolean processed;
|
boolean processed;
|
||||||
|
|
||||||
switch (mState) {
|
switch (mState) {
|
||||||
case SipSessionState.REGISTERING:
|
case SipSession.State.REGISTERING:
|
||||||
case SipSessionState.DEREGISTERING:
|
case SipSession.State.DEREGISTERING:
|
||||||
processed = registeringToReady(evt);
|
processed = registeringToReady(evt);
|
||||||
break;
|
break;
|
||||||
case SipSessionState.PINGING:
|
case SipSession.State.PINGING:
|
||||||
processed = keepAliveProcess(evt);
|
processed = keepAliveProcess(evt);
|
||||||
break;
|
break;
|
||||||
case SipSessionState.READY_TO_CALL:
|
case SipSession.State.READY_TO_CALL:
|
||||||
processed = readyForCall(evt);
|
processed = readyForCall(evt);
|
||||||
break;
|
break;
|
||||||
case SipSessionState.INCOMING_CALL:
|
case SipSession.State.INCOMING_CALL:
|
||||||
processed = incomingCall(evt);
|
processed = incomingCall(evt);
|
||||||
break;
|
break;
|
||||||
case SipSessionState.INCOMING_CALL_ANSWERING:
|
case SipSession.State.INCOMING_CALL_ANSWERING:
|
||||||
processed = incomingCallToInCall(evt);
|
processed = incomingCallToInCall(evt);
|
||||||
break;
|
break;
|
||||||
case SipSessionState.OUTGOING_CALL:
|
case SipSession.State.OUTGOING_CALL:
|
||||||
case SipSessionState.OUTGOING_CALL_RING_BACK:
|
case SipSession.State.OUTGOING_CALL_RING_BACK:
|
||||||
processed = outgoingCall(evt);
|
processed = outgoingCall(evt);
|
||||||
break;
|
break;
|
||||||
case SipSessionState.OUTGOING_CALL_CANCELING:
|
case SipSession.State.OUTGOING_CALL_CANCELING:
|
||||||
processed = outgoingCallToReady(evt);
|
processed = outgoingCallToReady(evt);
|
||||||
break;
|
break;
|
||||||
case SipSessionState.IN_CALL:
|
case SipSession.State.IN_CALL:
|
||||||
processed = inCall(evt);
|
processed = inCall(evt);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -650,8 +650,8 @@ class SipSessionGroup implements SipListener {
|
|||||||
private void processTransactionTerminated(
|
private void processTransactionTerminated(
|
||||||
TransactionTerminatedEvent event) {
|
TransactionTerminatedEvent event) {
|
||||||
switch (mState) {
|
switch (mState) {
|
||||||
case SipSessionState.IN_CALL:
|
case SipSession.State.IN_CALL:
|
||||||
case SipSessionState.READY_TO_CALL:
|
case SipSession.State.READY_TO_CALL:
|
||||||
Log.d(TAG, "Transaction terminated; do nothing");
|
Log.d(TAG, "Transaction terminated; do nothing");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -670,27 +670,27 @@ class SipSessionGroup implements SipListener {
|
|||||||
? event.getServerTransaction()
|
? event.getServerTransaction()
|
||||||
: event.getClientTransaction();
|
: event.getClientTransaction();
|
||||||
|
|
||||||
if ((current != target) && (mState != SipSessionState.PINGING)) {
|
if ((current != target) && (mState != SipSession.State.PINGING)) {
|
||||||
Log.d(TAG, "not the current transaction; current=" + current
|
Log.d(TAG, "not the current transaction; current=" + current
|
||||||
+ ", timed out=" + target);
|
+ ", timed out=" + target);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (mState) {
|
switch (mState) {
|
||||||
case SipSessionState.REGISTERING:
|
case SipSession.State.REGISTERING:
|
||||||
case SipSessionState.DEREGISTERING:
|
case SipSession.State.DEREGISTERING:
|
||||||
reset();
|
reset();
|
||||||
mProxy.onRegistrationTimeout(this);
|
mProxy.onRegistrationTimeout(this);
|
||||||
break;
|
break;
|
||||||
case SipSessionState.INCOMING_CALL:
|
case SipSession.State.INCOMING_CALL:
|
||||||
case SipSessionState.INCOMING_CALL_ANSWERING:
|
case SipSession.State.INCOMING_CALL_ANSWERING:
|
||||||
case SipSessionState.OUTGOING_CALL:
|
case SipSession.State.OUTGOING_CALL:
|
||||||
case SipSessionState.OUTGOING_CALL_CANCELING:
|
case SipSession.State.OUTGOING_CALL_CANCELING:
|
||||||
onError(SipErrorCode.TIME_OUT, event.toString());
|
onError(SipErrorCode.TIME_OUT, event.toString());
|
||||||
break;
|
break;
|
||||||
case SipSessionState.PINGING:
|
case SipSession.State.PINGING:
|
||||||
reset();
|
reset();
|
||||||
mReRegisterFlag = true;
|
mReRegisterFlag = true;
|
||||||
mState = SipSessionState.READY_TO_CALL;
|
mState = SipSession.State.READY_TO_CALL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -764,7 +764,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
case Response.OK:
|
case Response.OK:
|
||||||
int state = mState;
|
int state = mState;
|
||||||
onRegistrationDone((state == SipSessionState.REGISTERING)
|
onRegistrationDone((state == SipSession.State.REGISTERING)
|
||||||
? getExpiryTime(((ResponseEvent) evt).getResponse())
|
? getExpiryTime(((ResponseEvent) evt).getResponse())
|
||||||
: -1);
|
: -1);
|
||||||
mLastNonce = null;
|
mLastNonce = null;
|
||||||
@ -851,7 +851,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
generateTag());
|
generateTag());
|
||||||
mDialog = mClientTransaction.getDialog();
|
mDialog = mClientTransaction.getDialog();
|
||||||
addSipSession(this);
|
addSipSession(this);
|
||||||
mState = SipSessionState.OUTGOING_CALL;
|
mState = SipSession.State.OUTGOING_CALL;
|
||||||
mProxy.onCalling(this);
|
mProxy.onCalling(this);
|
||||||
startSessionTimer(cmd.getTimeout());
|
startSessionTimer(cmd.getTimeout());
|
||||||
return true;
|
return true;
|
||||||
@ -861,7 +861,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
generateTag(), duration);
|
generateTag(), duration);
|
||||||
mDialog = mClientTransaction.getDialog();
|
mDialog = mClientTransaction.getDialog();
|
||||||
addSipSession(this);
|
addSipSession(this);
|
||||||
mState = SipSessionState.REGISTERING;
|
mState = SipSession.State.REGISTERING;
|
||||||
mProxy.onRegistering(this);
|
mProxy.onRegistering(this);
|
||||||
return true;
|
return true;
|
||||||
} else if (DEREGISTER == evt) {
|
} else if (DEREGISTER == evt) {
|
||||||
@ -869,7 +869,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
generateTag(), 0);
|
generateTag(), 0);
|
||||||
mDialog = mClientTransaction.getDialog();
|
mDialog = mClientTransaction.getDialog();
|
||||||
addSipSession(this);
|
addSipSession(this);
|
||||||
mState = SipSessionState.DEREGISTERING;
|
mState = SipSession.State.DEREGISTERING;
|
||||||
mProxy.onRegistering(this);
|
mProxy.onRegistering(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -884,7 +884,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
mLocalProfile,
|
mLocalProfile,
|
||||||
((MakeCallCommand) evt).getSessionDescription(),
|
((MakeCallCommand) evt).getSessionDescription(),
|
||||||
mServerTransaction);
|
mServerTransaction);
|
||||||
mState = SipSessionState.INCOMING_CALL_ANSWERING;
|
mState = SipSession.State.INCOMING_CALL_ANSWERING;
|
||||||
startSessionTimer(((MakeCallCommand) evt).getTimeout());
|
startSessionTimer(((MakeCallCommand) evt).getTimeout());
|
||||||
return true;
|
return true;
|
||||||
} else if (END_CALL == evt) {
|
} else if (END_CALL == evt) {
|
||||||
@ -925,8 +925,8 @@ class SipSessionGroup implements SipListener {
|
|||||||
int statusCode = response.getStatusCode();
|
int statusCode = response.getStatusCode();
|
||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
case Response.RINGING:
|
case Response.RINGING:
|
||||||
if (mState == SipSessionState.OUTGOING_CALL) {
|
if (mState == SipSession.State.OUTGOING_CALL) {
|
||||||
mState = SipSessionState.OUTGOING_CALL_RING_BACK;
|
mState = SipSession.State.OUTGOING_CALL_RING_BACK;
|
||||||
mProxy.onRingingBack(this);
|
mProxy.onRingingBack(this);
|
||||||
cancelSessionTimer();
|
cancelSessionTimer();
|
||||||
}
|
}
|
||||||
@ -969,7 +969,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
// response comes back yet. We are cheating for not checking
|
// response comes back yet. We are cheating for not checking
|
||||||
// response.
|
// response.
|
||||||
mSipHelper.sendCancel(mClientTransaction);
|
mSipHelper.sendCancel(mClientTransaction);
|
||||||
mState = SipSessionState.OUTGOING_CALL_CANCELING;
|
mState = SipSession.State.OUTGOING_CALL_CANCELING;
|
||||||
startSessionTimer(CANCEL_CALL_TIMER);
|
startSessionTimer(CANCEL_CALL_TIMER);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1025,7 +1025,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
} else if (isRequestEvent(Request.INVITE, evt)) {
|
} else if (isRequestEvent(Request.INVITE, evt)) {
|
||||||
// got Re-INVITE
|
// got Re-INVITE
|
||||||
RequestEvent event = mInviteReceived = (RequestEvent) evt;
|
RequestEvent event = mInviteReceived = (RequestEvent) evt;
|
||||||
mState = SipSessionState.INCOMING_CALL;
|
mState = SipSession.State.INCOMING_CALL;
|
||||||
mPeerSessionDescription = extractContent(event.getRequest());
|
mPeerSessionDescription = extractContent(event.getRequest());
|
||||||
mServerTransaction = null;
|
mServerTransaction = null;
|
||||||
mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
|
mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
|
||||||
@ -1038,7 +1038,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
// to change call
|
// to change call
|
||||||
mClientTransaction = mSipHelper.sendReinvite(mDialog,
|
mClientTransaction = mSipHelper.sendReinvite(mDialog,
|
||||||
((MakeCallCommand) evt).getSessionDescription());
|
((MakeCallCommand) evt).getSessionDescription());
|
||||||
mState = SipSessionState.OUTGOING_CALL;
|
mState = SipSession.State.OUTGOING_CALL;
|
||||||
startSessionTimer(((MakeCallCommand) evt).getTimeout());
|
startSessionTimer(((MakeCallCommand) evt).getTimeout());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1066,14 +1066,14 @@ class SipSessionGroup implements SipListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void establishCall() {
|
private void establishCall() {
|
||||||
mState = SipSessionState.IN_CALL;
|
mState = SipSession.State.IN_CALL;
|
||||||
mInCall = true;
|
mInCall = true;
|
||||||
cancelSessionTimer();
|
cancelSessionTimer();
|
||||||
mProxy.onCallEstablished(this, mPeerSessionDescription);
|
mProxy.onCallEstablished(this, mPeerSessionDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fallbackToPreviousInCall(int errorCode, String message) {
|
private void fallbackToPreviousInCall(int errorCode, String message) {
|
||||||
mState = SipSessionState.IN_CALL;
|
mState = SipSession.State.IN_CALL;
|
||||||
mProxy.onCallChangeFailed(this, errorCode, message);
|
mProxy.onCallChangeFailed(this, errorCode, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1095,8 +1095,8 @@ class SipSessionGroup implements SipListener {
|
|||||||
private void onError(int errorCode, String message) {
|
private void onError(int errorCode, String message) {
|
||||||
cancelSessionTimer();
|
cancelSessionTimer();
|
||||||
switch (mState) {
|
switch (mState) {
|
||||||
case SipSessionState.REGISTERING:
|
case SipSession.State.REGISTERING:
|
||||||
case SipSessionState.DEREGISTERING:
|
case SipSession.State.DEREGISTERING:
|
||||||
onRegistrationFailed(errorCode, message);
|
onRegistrationFailed(errorCode, message);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -1270,7 +1270,7 @@ class SipSessionGroup implements SipListener {
|
|||||||
private static boolean isLoggable(SipSessionImpl s) {
|
private static boolean isLoggable(SipSessionImpl s) {
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
switch (s.mState) {
|
switch (s.mState) {
|
||||||
case SipSessionState.PINGING:
|
case SipSession.State.PINGING:
|
||||||
return DEBUG_PING;
|
return DEBUG_PING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub {
|
|||||||
// One thread for each calling back.
|
// One thread for each calling back.
|
||||||
// Note: Guarantee ordering if the issue becomes important. Currently,
|
// Note: Guarantee ordering if the issue becomes important. Currently,
|
||||||
// the chance of handling two callback events at a time is none.
|
// the chance of handling two callback events at a time is none.
|
||||||
new Thread(runnable).start();
|
new Thread(runnable, "SipSessionCallbackThread").start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCalling(final ISipSession session) {
|
public void onCalling(final ISipSession session) {
|
||||||
|
@ -27,7 +27,7 @@ import android.net.sip.SipErrorCode;
|
|||||||
import android.net.sip.SipException;
|
import android.net.sip.SipException;
|
||||||
import android.net.sip.SipManager;
|
import android.net.sip.SipManager;
|
||||||
import android.net.sip.SipProfile;
|
import android.net.sip.SipProfile;
|
||||||
import android.net.sip.SipSessionState;
|
import android.net.sip.SipSession;
|
||||||
import android.os.AsyncResult;
|
import android.os.AsyncResult;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@ -92,7 +92,7 @@ public class SipPhone extends SipPhoneBase {
|
|||||||
foregroundCall = new SipCall();
|
foregroundCall = new SipCall();
|
||||||
backgroundCall = new SipCall();
|
backgroundCall = new SipCall();
|
||||||
mProfile = profile;
|
mProfile = profile;
|
||||||
mSipManager = SipManager.getInstance(context);
|
mSipManager = SipManager.newInstance(context);
|
||||||
|
|
||||||
// FIXME: what's this for SIP?
|
// FIXME: what's this for SIP?
|
||||||
//Change the system property
|
//Change the system property
|
||||||
@ -707,8 +707,8 @@ public class SipPhone extends SipPhoneBase {
|
|||||||
|
|
||||||
void dial() throws SipException {
|
void dial() throws SipException {
|
||||||
setState(Call.State.DIALING);
|
setState(Call.State.DIALING);
|
||||||
mSipAudioCall = mSipManager.makeAudioCall(mContext, mProfile,
|
mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
|
||||||
mPeer, null, SESSION_TIMEOUT);
|
SESSION_TIMEOUT);
|
||||||
mSipAudioCall.setRingbackToneEnabled(false);
|
mSipAudioCall.setRingbackToneEnabled(false);
|
||||||
mSipAudioCall.setListener(mAdapter);
|
mSipAudioCall.setListener(mAdapter);
|
||||||
}
|
}
|
||||||
@ -808,20 +808,20 @@ public class SipPhone extends SipPhoneBase {
|
|||||||
if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
|
if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
|
||||||
int sessionState = sipAudioCall.getState();
|
int sessionState = sipAudioCall.getState();
|
||||||
switch (sessionState) {
|
switch (sessionState) {
|
||||||
case SipSessionState.READY_TO_CALL: return Call.State.IDLE;
|
case SipSession.State.READY_TO_CALL: return Call.State.IDLE;
|
||||||
case SipSessionState.INCOMING_CALL:
|
case SipSession.State.INCOMING_CALL:
|
||||||
case SipSessionState.INCOMING_CALL_ANSWERING: return Call.State.INCOMING;
|
case SipSession.State.INCOMING_CALL_ANSWERING: return Call.State.INCOMING;
|
||||||
case SipSessionState.OUTGOING_CALL: return Call.State.DIALING;
|
case SipSession.State.OUTGOING_CALL: return Call.State.DIALING;
|
||||||
case SipSessionState.OUTGOING_CALL_RING_BACK: return Call.State.ALERTING;
|
case SipSession.State.OUTGOING_CALL_RING_BACK: return Call.State.ALERTING;
|
||||||
case SipSessionState.OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING;
|
case SipSession.State.OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING;
|
||||||
case SipSessionState.IN_CALL: return Call.State.ACTIVE;
|
case SipSession.State.IN_CALL: return Call.State.ACTIVE;
|
||||||
default:
|
default:
|
||||||
Log.w(LOG_TAG, "illegal connection state: " + sessionState);
|
Log.w(LOG_TAG, "illegal connection state: " + sessionState);
|
||||||
return Call.State.DISCONNECTED;
|
return Call.State.DISCONNECTED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class SipAudioCallAdapter extends SipAudioCall.Adapter {
|
private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
|
||||||
protected abstract void onCallEnded(Connection.DisconnectCause cause);
|
protected abstract void onCallEnded(Connection.DisconnectCause cause);
|
||||||
protected abstract void onError(Connection.DisconnectCause cause);
|
protected abstract void onError(Connection.DisconnectCause cause);
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,766 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.net.sip;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.Ringtone;
|
|
||||||
import android.media.RingtoneManager;
|
|
||||||
import android.media.ToneGenerator;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.net.rtp.AudioCodec;
|
|
||||||
import android.net.rtp.AudioGroup;
|
|
||||||
import android.net.rtp.AudioStream;
|
|
||||||
import android.net.rtp.RtpStream;
|
|
||||||
import android.net.sip.SimpleSessionDescription.Media;
|
|
||||||
import android.net.wifi.WifiManager;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class that handles an audio call over SIP.
|
|
||||||
*/
|
|
||||||
/** @hide */
|
|
||||||
public class SipAudioCallImpl extends SipSessionAdapter
|
|
||||||
implements SipAudioCall {
|
|
||||||
private static final String TAG = SipAudioCallImpl.class.getSimpleName();
|
|
||||||
private static final boolean RELEASE_SOCKET = true;
|
|
||||||
private static final boolean DONT_RELEASE_SOCKET = false;
|
|
||||||
private static final int SESSION_TIMEOUT = 5; // in seconds
|
|
||||||
|
|
||||||
private Context mContext;
|
|
||||||
private SipProfile mLocalProfile;
|
|
||||||
private SipAudioCall.Listener mListener;
|
|
||||||
private ISipSession mSipSession;
|
|
||||||
|
|
||||||
private long mSessionId = System.currentTimeMillis();
|
|
||||||
private String mPeerSd;
|
|
||||||
|
|
||||||
private AudioStream mAudioStream;
|
|
||||||
private AudioGroup mAudioGroup;
|
|
||||||
|
|
||||||
private boolean mInCall = false;
|
|
||||||
private boolean mMuted = false;
|
|
||||||
private boolean mHold = false;
|
|
||||||
|
|
||||||
private boolean mRingbackToneEnabled = true;
|
|
||||||
private boolean mRingtoneEnabled = true;
|
|
||||||
private Ringtone mRingtone;
|
|
||||||
private ToneGenerator mRingbackTone;
|
|
||||||
|
|
||||||
private SipProfile mPendingCallRequest;
|
|
||||||
private WifiManager mWm;
|
|
||||||
private WifiManager.WifiLock mWifiHighPerfLock;
|
|
||||||
|
|
||||||
private int mErrorCode = SipErrorCode.NO_ERROR;
|
|
||||||
private String mErrorMessage;
|
|
||||||
|
|
||||||
public SipAudioCallImpl(Context context, SipProfile localProfile) {
|
|
||||||
mContext = context;
|
|
||||||
mLocalProfile = localProfile;
|
|
||||||
mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void grabWifiHighPerfLock() {
|
|
||||||
if (mWifiHighPerfLock == null) {
|
|
||||||
Log.v(TAG, "acquire wifi high perf lock");
|
|
||||||
mWifiHighPerfLock = ((WifiManager)
|
|
||||||
mContext.getSystemService(Context.WIFI_SERVICE))
|
|
||||||
.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
|
|
||||||
mWifiHighPerfLock.acquire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseWifiHighPerfLock() {
|
|
||||||
if (mWifiHighPerfLock != null) {
|
|
||||||
Log.v(TAG, "release wifi high perf lock");
|
|
||||||
mWifiHighPerfLock.release();
|
|
||||||
mWifiHighPerfLock = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isWifiOn() {
|
|
||||||
return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (isWifiOn()) grabWifiHighPerfLock();
|
|
||||||
|
|
||||||
if (!mHold) {
|
|
||||||
/* The recorder volume will be very low if the device is in
|
|
||||||
* IN_CALL mode. Therefore, we have to set the mode to NORMAL
|
|
||||||
* in order to have the normal microphone level.
|
|
||||||
*/
|
|
||||||
((AudioManager) mContext.getSystemService
|
|
||||||
(Context.AUDIO_SERVICE))
|
|
||||||
.setMode(AudioManager.MODE_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// AudioGroup logic:
|
|
||||||
AudioGroup audioGroup = getAudioGroup();
|
|
||||||
if (mHold) {
|
|
||||||
if (audioGroup != null) {
|
|
||||||
audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
|
|
||||||
}
|
|
||||||
// don't create an AudioGroup here; doing so will fail if
|
|
||||||
// there's another AudioGroup out there that's active
|
|
||||||
} else {
|
|
||||||
if (audioGroup == null) audioGroup = new AudioGroup();
|
|
||||||
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");
|
|
||||||
releaseWifiHighPerfLock();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,8 +30,9 @@ import java.text.ParseException;
|
|||||||
* The class provides API for various SIP related tasks. Specifically, the API
|
* The class provides API for various SIP related tasks. Specifically, the API
|
||||||
* allows an application to:
|
* allows an application to:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>register a {@link SipProfile} to have the background SIP service listen
|
* <li>open a {@link SipProfile} to get ready for making outbound calls or have
|
||||||
* to incoming calls and broadcast them with registered command string. See
|
* the background SIP service listen to incoming calls and broadcast them
|
||||||
|
* with registered command string. See
|
||||||
* {@link #open(SipProfile, String, SipRegistrationListener)},
|
* {@link #open(SipProfile, String, SipRegistrationListener)},
|
||||||
* {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and
|
* {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and
|
||||||
* {@link #isRegistered}. It also facilitates handling of the incoming call
|
* {@link #isRegistered}. It also facilitates handling of the incoming call
|
||||||
@ -40,39 +41,59 @@ import java.text.ParseException;
|
|||||||
* {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li>
|
* {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li>
|
||||||
* <li>make/take SIP-based audio calls. See
|
* <li>make/take SIP-based audio calls. See
|
||||||
* {@link #makeAudioCall} and {@link #takeAudioCall}.</li>
|
* {@link #makeAudioCall} and {@link #takeAudioCall}.</li>
|
||||||
* <li>register/unregister with a SIP service provider. See
|
* <li>register/unregister with a SIP service provider manually. See
|
||||||
* {@link #register} and {@link #unregister}.</li>
|
* {@link #register} and {@link #unregister}.</li>
|
||||||
* <li>process SIP events directly with a {@link ISipSession} created by
|
* <li>process SIP events directly with a {@link SipSession} created by
|
||||||
* {@link #createSipSession}.</li>
|
* {@link #createSipSession}.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public class SipManager {
|
public class SipManager {
|
||||||
/** @hide */
|
/**
|
||||||
public static final String SIP_INCOMING_CALL_ACTION =
|
* Action string for the incoming call intent for the Phone app.
|
||||||
|
* Internal use only.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String ACTION_SIP_INCOMING_CALL =
|
||||||
"com.android.phone.SIP_INCOMING_CALL";
|
"com.android.phone.SIP_INCOMING_CALL";
|
||||||
/** @hide */
|
/**
|
||||||
public static final String SIP_ADD_PHONE_ACTION =
|
* Action string for the add-phone intent.
|
||||||
|
* Internal use only.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String ACTION_SIP_ADD_PHONE =
|
||||||
"com.android.phone.SIP_ADD_PHONE";
|
"com.android.phone.SIP_ADD_PHONE";
|
||||||
/** @hide */
|
/**
|
||||||
public static final String SIP_REMOVE_PHONE_ACTION =
|
* Action string for the remove-phone intent.
|
||||||
|
* Internal use only.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String ACTION_SIP_REMOVE_PHONE =
|
||||||
"com.android.phone.SIP_REMOVE_PHONE";
|
"com.android.phone.SIP_REMOVE_PHONE";
|
||||||
/** @hide */
|
/**
|
||||||
public static final String LOCAL_URI_KEY = "LOCAL SIPURI";
|
* Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
|
||||||
|
* Internal use only.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String EXTRA_LOCAL_URI = "android:localSipUri";
|
||||||
|
|
||||||
private static final String CALL_ID_KEY = "CallID";
|
/** Part of the incoming call intent. */
|
||||||
private static final String OFFER_SD_KEY = "OfferSD";
|
public static final String EXTRA_CALL_ID = "android:sipCallID";
|
||||||
|
|
||||||
|
/** Part of the incoming call intent. */
|
||||||
|
public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
|
||||||
|
|
||||||
private ISipService mSipService;
|
private ISipService mSipService;
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a manager instance. Returns null if SIP API is not supported.
|
* Creates a manager instance. Returns null if SIP API is not supported.
|
||||||
*
|
*
|
||||||
* @param context application context for checking if SIP API is supported
|
* @param context application context for creating the manager object
|
||||||
* @return the manager instance or null if SIP API is not supported
|
* @return the manager instance or null if SIP API is not supported
|
||||||
*/
|
*/
|
||||||
public static SipManager getInstance(Context context) {
|
public static SipManager newInstance(Context context) {
|
||||||
return (isApiSupported(context) ? new SipManager() : null);
|
return (isApiSupported(context) ? new SipManager(context) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,7 +101,7 @@ public class SipManager {
|
|||||||
*/
|
*/
|
||||||
public static boolean isApiSupported(Context context) {
|
public static boolean isApiSupported(Context context) {
|
||||||
return true;
|
return true;
|
||||||
/*
|
/* TODO: uncomment this before ship
|
||||||
return context.getPackageManager().hasSystemFeature(
|
return context.getPackageManager().hasSystemFeature(
|
||||||
PackageManager.FEATURE_SIP);
|
PackageManager.FEATURE_SIP);
|
||||||
*/
|
*/
|
||||||
@ -91,7 +112,7 @@ public class SipManager {
|
|||||||
*/
|
*/
|
||||||
public static boolean isVoipSupported(Context context) {
|
public static boolean isVoipSupported(Context context) {
|
||||||
return true;
|
return true;
|
||||||
/*
|
/* TODO: uncomment this before ship
|
||||||
return context.getPackageManager().hasSystemFeature(
|
return context.getPackageManager().hasSystemFeature(
|
||||||
PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
|
PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
|
||||||
*/
|
*/
|
||||||
@ -105,23 +126,21 @@ public class SipManager {
|
|||||||
com.android.internal.R.bool.config_sip_wifi_only);
|
com.android.internal.R.bool.config_sip_wifi_only);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SipManager() {
|
private SipManager(Context context) {
|
||||||
|
mContext = context;
|
||||||
createSipService();
|
createSipService();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createSipService() {
|
private void createSipService() {
|
||||||
if (mSipService != null) return;
|
|
||||||
IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
|
IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
|
||||||
mSipService = ISipService.Stub.asInterface(b);
|
mSipService = ISipService.Stub.asInterface(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the profile for making calls and/or receiving calls. Subsequent
|
* Opens the profile for making calls. The caller may make subsequent calls
|
||||||
* SIP calls can be made through the default phone UI. The caller may also
|
* through {@link #makeAudioCall}. If one also wants to receive calls on the
|
||||||
* make subsequent calls through {@link #makeAudioCall}.
|
* profile, use {@link #open(SipProfile, String, SipRegistrationListener)}
|
||||||
* If the receiving-call option is enabled in the profile, the SIP service
|
* instead.
|
||||||
* will register the profile to the corresponding server periodically in
|
|
||||||
* order to receive calls from the server.
|
|
||||||
*
|
*
|
||||||
* @param localProfile the SIP profile to make calls from
|
* @param localProfile the SIP profile to make calls from
|
||||||
* @throws SipException if the profile contains incorrect settings or
|
* @throws SipException if the profile contains incorrect settings or
|
||||||
@ -136,12 +155,11 @@ public class SipManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the profile for making calls and/or receiving calls. Subsequent
|
* Opens the profile for making calls and/or receiving calls. The caller may
|
||||||
* SIP calls can be made through the default phone UI. The caller may also
|
* make subsequent calls through {@link #makeAudioCall}. If the
|
||||||
* make subsequent calls through {@link #makeAudioCall}.
|
* auto-registration option is enabled in the profile, the SIP service
|
||||||
* If the receiving-call option is enabled in the profile, the SIP service
|
* will register the profile to the corresponding SIP provider periodically
|
||||||
* will register the profile to the corresponding server periodically in
|
* in order to receive calls from the provider.
|
||||||
* order to receive calls from the server.
|
|
||||||
*
|
*
|
||||||
* @param localProfile the SIP profile to receive incoming calls for
|
* @param localProfile the SIP profile to receive incoming calls for
|
||||||
* @param incomingCallBroadcastAction the action to be broadcast when an
|
* @param incomingCallBroadcastAction the action to be broadcast when an
|
||||||
@ -195,7 +213,8 @@ public class SipManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the specified profile is enabled to receive calls.
|
* Checks if the specified profile is opened in the SIP service for
|
||||||
|
* making and/or receiving calls.
|
||||||
*
|
*
|
||||||
* @param localProfileUri the URI of the profile in question
|
* @param localProfileUri the URI of the profile in question
|
||||||
* @return true if the profile is enabled to receive calls
|
* @return true if the profile is enabled to receive calls
|
||||||
@ -210,11 +229,16 @@ public class SipManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the specified profile is registered to the server for
|
* Checks if the SIP service has successfully registered the profile to the
|
||||||
* receiving calls.
|
* SIP provider (specified in the profile) for receiving calls. Returning
|
||||||
|
* true from this method also implies the profile is opened
|
||||||
|
* ({@link #isOpened}).
|
||||||
*
|
*
|
||||||
* @param localProfileUri the URI of the profile in question
|
* @param localProfileUri the URI of the profile in question
|
||||||
* @return true if the profile is registered to the server
|
* @return true if the profile is registered to the SIP provider; false if
|
||||||
|
* the profile has not been opened in the SIP service or the SIP
|
||||||
|
* service has not yet successfully registered the profile to the SIP
|
||||||
|
* provider
|
||||||
* @throws SipException if calling the SIP service results in an error
|
* @throws SipException if calling the SIP service results in an error
|
||||||
*/
|
*/
|
||||||
public boolean isRegistered(String localProfileUri) throws SipException {
|
public boolean isRegistered(String localProfileUri) throws SipException {
|
||||||
@ -231,7 +255,6 @@ public class SipManager {
|
|||||||
* {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
|
* {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
|
||||||
* will be called.
|
* will be called.
|
||||||
*
|
*
|
||||||
* @param context context to create a {@link SipAudioCall} object
|
|
||||||
* @param localProfile the SIP profile to make the call from
|
* @param localProfile the SIP profile to make the call from
|
||||||
* @param peerProfile the SIP profile to make the call to
|
* @param peerProfile the SIP profile to make the call to
|
||||||
* @param listener to listen to the call events from {@link SipAudioCall};
|
* @param listener to listen to the call events from {@link SipAudioCall};
|
||||||
@ -241,10 +264,10 @@ public class SipManager {
|
|||||||
* @throws SipException if calling the SIP service results in an error
|
* @throws SipException if calling the SIP service results in an error
|
||||||
* @see SipAudioCall.Listener.onError
|
* @see SipAudioCall.Listener.onError
|
||||||
*/
|
*/
|
||||||
public SipAudioCall makeAudioCall(Context context, SipProfile localProfile,
|
public SipAudioCall makeAudioCall(SipProfile localProfile,
|
||||||
SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
|
SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
|
||||||
throws SipException {
|
throws SipException {
|
||||||
SipAudioCall call = new SipAudioCallImpl(context, localProfile);
|
SipAudioCall call = new SipAudioCall(mContext, localProfile);
|
||||||
call.setListener(listener);
|
call.setListener(listener);
|
||||||
call.makeCall(peerProfile, this, timeout);
|
call.makeCall(peerProfile, this, timeout);
|
||||||
return call;
|
return call;
|
||||||
@ -257,7 +280,6 @@ public class SipManager {
|
|||||||
* {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
|
* {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
|
||||||
* will be called.
|
* will be called.
|
||||||
*
|
*
|
||||||
* @param context context to create a {@link SipAudioCall} object
|
|
||||||
* @param localProfileUri URI of the SIP profile to make the call from
|
* @param localProfileUri URI of the SIP profile to make the call from
|
||||||
* @param peerProfileUri URI of the SIP profile to make the call to
|
* @param peerProfileUri URI of the SIP profile to make the call to
|
||||||
* @param listener to listen to the call events from {@link SipAudioCall};
|
* @param listener to listen to the call events from {@link SipAudioCall};
|
||||||
@ -267,11 +289,11 @@ public class SipManager {
|
|||||||
* @throws SipException if calling the SIP service results in an error
|
* @throws SipException if calling the SIP service results in an error
|
||||||
* @see SipAudioCall.Listener.onError
|
* @see SipAudioCall.Listener.onError
|
||||||
*/
|
*/
|
||||||
public SipAudioCall makeAudioCall(Context context, String localProfileUri,
|
public SipAudioCall makeAudioCall(String localProfileUri,
|
||||||
String peerProfileUri, SipAudioCall.Listener listener, int timeout)
|
String peerProfileUri, SipAudioCall.Listener listener, int timeout)
|
||||||
throws SipException {
|
throws SipException {
|
||||||
try {
|
try {
|
||||||
return makeAudioCall(context,
|
return makeAudioCall(
|
||||||
new SipProfile.Builder(localProfileUri).build(),
|
new SipProfile.Builder(localProfileUri).build(),
|
||||||
new SipProfile.Builder(peerProfileUri).build(), listener,
|
new SipProfile.Builder(peerProfileUri).build(), listener,
|
||||||
timeout);
|
timeout);
|
||||||
@ -281,15 +303,14 @@ public class SipManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method calls {@code takeAudioCall(context, incomingCallIntent,
|
* The method calls {@code takeAudioCall(incomingCallIntent,
|
||||||
* listener, true}.
|
* listener, true}.
|
||||||
*
|
*
|
||||||
* @see #takeAudioCall(Context, Intent, SipAudioCall.Listener, boolean)
|
* @see #takeAudioCall(Intent, SipAudioCall.Listener, boolean)
|
||||||
*/
|
*/
|
||||||
public SipAudioCall takeAudioCall(Context context,
|
public SipAudioCall takeAudioCall(Intent incomingCallIntent,
|
||||||
Intent incomingCallIntent, SipAudioCall.Listener listener)
|
SipAudioCall.Listener listener) throws SipException {
|
||||||
throws SipException {
|
return takeAudioCall(incomingCallIntent, listener, true);
|
||||||
return takeAudioCall(context, incomingCallIntent, listener, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -298,16 +319,15 @@ public class SipManager {
|
|||||||
* {@link SipAudioCall.Listener#onRinging}
|
* {@link SipAudioCall.Listener#onRinging}
|
||||||
* callback.
|
* callback.
|
||||||
*
|
*
|
||||||
* @param context context to create a {@link SipAudioCall} object
|
|
||||||
* @param incomingCallIntent the incoming call broadcast intent
|
* @param incomingCallIntent the incoming call broadcast intent
|
||||||
* @param listener to listen to the call events from {@link SipAudioCall};
|
* @param listener to listen to the call events from {@link SipAudioCall};
|
||||||
* can be null
|
* can be null
|
||||||
* @return a {@link SipAudioCall} object
|
* @return a {@link SipAudioCall} object
|
||||||
* @throws SipException if calling the SIP service results in an error
|
* @throws SipException if calling the SIP service results in an error
|
||||||
*/
|
*/
|
||||||
public SipAudioCall takeAudioCall(Context context,
|
public SipAudioCall takeAudioCall(Intent incomingCallIntent,
|
||||||
Intent incomingCallIntent, SipAudioCall.Listener listener,
|
SipAudioCall.Listener listener, boolean ringtoneEnabled)
|
||||||
boolean ringtoneEnabled) throws SipException {
|
throws SipException {
|
||||||
if (incomingCallIntent == null) return null;
|
if (incomingCallIntent == null) return null;
|
||||||
|
|
||||||
String callId = getCallId(incomingCallIntent);
|
String callId = getCallId(incomingCallIntent);
|
||||||
@ -324,10 +344,10 @@ public class SipManager {
|
|||||||
try {
|
try {
|
||||||
ISipSession session = mSipService.getPendingSession(callId);
|
ISipSession session = mSipService.getPendingSession(callId);
|
||||||
if (session == null) return null;
|
if (session == null) return null;
|
||||||
SipAudioCall call = new SipAudioCallImpl(
|
SipAudioCall call = new SipAudioCall(
|
||||||
context, session.getLocalProfile());
|
mContext, session.getLocalProfile());
|
||||||
call.setRingtoneEnabled(ringtoneEnabled);
|
call.setRingtoneEnabled(ringtoneEnabled);
|
||||||
call.attachCall(session, offerSd);
|
call.attachCall(new SipSession(session), offerSd);
|
||||||
call.setListener(listener);
|
call.setListener(listener);
|
||||||
return call;
|
return call;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
@ -355,7 +375,7 @@ public class SipManager {
|
|||||||
* @return the call ID or null if the intent does not contain it
|
* @return the call ID or null if the intent does not contain it
|
||||||
*/
|
*/
|
||||||
public static String getCallId(Intent incomingCallIntent) {
|
public static String getCallId(Intent incomingCallIntent) {
|
||||||
return incomingCallIntent.getStringExtra(CALL_ID_KEY);
|
return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -367,30 +387,30 @@ public class SipManager {
|
|||||||
* have it
|
* have it
|
||||||
*/
|
*/
|
||||||
public static String getOfferSessionDescription(Intent incomingCallIntent) {
|
public static String getOfferSessionDescription(Intent incomingCallIntent) {
|
||||||
return incomingCallIntent.getStringExtra(OFFER_SD_KEY);
|
return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an incoming call broadcast intent.
|
* Creates an incoming call broadcast intent.
|
||||||
*
|
*
|
||||||
* @param action the action string to broadcast
|
|
||||||
* @param callId the call ID of the incoming call
|
* @param callId the call ID of the incoming call
|
||||||
* @param sessionDescription the session description of the incoming call
|
* @param sessionDescription the session description of the incoming call
|
||||||
* @return the incoming call intent
|
* @return the incoming call intent
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public static Intent createIncomingCallBroadcast(String action,
|
public static Intent createIncomingCallBroadcast(String callId,
|
||||||
String callId, String sessionDescription) {
|
String sessionDescription) {
|
||||||
Intent intent = new Intent(action);
|
Intent intent = new Intent();
|
||||||
intent.putExtra(CALL_ID_KEY, callId);
|
intent.putExtra(EXTRA_CALL_ID, callId);
|
||||||
intent.putExtra(OFFER_SD_KEY, sessionDescription);
|
intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the profile to the corresponding server for receiving calls.
|
* Manually registers the profile to the corresponding SIP provider for
|
||||||
* {@link #open} is still needed to be called at least once in order for
|
* receiving calls. {@link #open(SipProfile, String, SipRegistrationListener)}
|
||||||
* the SIP service to broadcast an intent when an incoming call is received.
|
* is still needed to be called at least once in order for the SIP service
|
||||||
|
* to broadcast an intent when an incoming call is received.
|
||||||
*
|
*
|
||||||
* @param localProfile the SIP profile to register with
|
* @param localProfile the SIP profile to register with
|
||||||
* @param expiryTime registration expiration time (in seconds)
|
* @param expiryTime registration expiration time (in seconds)
|
||||||
@ -409,8 +429,10 @@ public class SipManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregisters the profile from the corresponding server for not receiving
|
* Manually unregisters the profile from the corresponding SIP provider for
|
||||||
* further calls.
|
* stop receiving further calls. This may interference with the auto
|
||||||
|
* registration process in the SIP service if the auto-registration option
|
||||||
|
* in the profile is enabled.
|
||||||
*
|
*
|
||||||
* @param localProfile the SIP profile to register with
|
* @param localProfile the SIP profile to register with
|
||||||
* @param listener to listen to the registration events
|
* @param listener to listen to the registration events
|
||||||
@ -460,10 +482,11 @@ public class SipManager {
|
|||||||
* @param localProfile the SIP profile the session is associated with
|
* @param localProfile the SIP profile the session is associated with
|
||||||
* @param listener to listen to SIP session events
|
* @param listener to listen to SIP session events
|
||||||
*/
|
*/
|
||||||
public ISipSession createSipSession(SipProfile localProfile,
|
public SipSession createSipSession(SipProfile localProfile,
|
||||||
ISipSessionListener listener) throws SipException {
|
SipSession.Listener listener) throws SipException {
|
||||||
try {
|
try {
|
||||||
return mSipService.createSession(localProfile, listener);
|
ISipSession s = mSipService.createSession(localProfile, null);
|
||||||
|
return new SipSession(s, listener);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new SipException("createSipSession()", e);
|
throw new SipException("createSipSession()", e);
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,6 @@ public class SipProfile implements Parcelable, Serializable, Cloneable {
|
|||||||
private boolean mAutoRegistration = true;
|
private boolean mAutoRegistration = true;
|
||||||
private transient int mCallingUid = 0;
|
private transient int mCallingUid = 0;
|
||||||
|
|
||||||
/** @hide */
|
|
||||||
public static final Parcelable.Creator<SipProfile> CREATOR =
|
public static final Parcelable.Creator<SipProfile> CREATOR =
|
||||||
new Parcelable.Creator<SipProfile>() {
|
new Parcelable.Creator<SipProfile>() {
|
||||||
public SipProfile createFromParcel(Parcel in) {
|
public SipProfile createFromParcel(Parcel in) {
|
||||||
@ -287,7 +286,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable {
|
|||||||
mCallingUid = in.readInt();
|
mCallingUid = in.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
@Override
|
||||||
public void writeToParcel(Parcel out, int flags) {
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
out.writeSerializable(mAddress);
|
out.writeSerializable(mAddress);
|
||||||
out.writeString(mProxyAddress);
|
out.writeString(mProxyAddress);
|
||||||
@ -300,7 +299,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable {
|
|||||||
out.writeInt(mCallingUid);
|
out.writeInt(mCallingUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
531
voip/java/android/net/sip/SipSession.java
Normal file
531
voip/java/android/net/sip/SipSession.java
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.net.sip;
|
||||||
|
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SIP session that is associated with a SIP dialog or a standalone
|
||||||
|
* transaction not within a dialog.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public final class SipSession {
|
||||||
|
private static final String TAG = "SipSession";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines {@link SipSession} states.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static class State {
|
||||||
|
/** When session is ready to initiate a call or transaction. */
|
||||||
|
public static final int READY_TO_CALL = 0;
|
||||||
|
|
||||||
|
/** When the registration request is sent out. */
|
||||||
|
public static final int REGISTERING = 1;
|
||||||
|
|
||||||
|
/** When the unregistration request is sent out. */
|
||||||
|
public static final int DEREGISTERING = 2;
|
||||||
|
|
||||||
|
/** When an INVITE request is received. */
|
||||||
|
public static final int INCOMING_CALL = 3;
|
||||||
|
|
||||||
|
/** When an OK response is sent for the INVITE request received. */
|
||||||
|
public static final int INCOMING_CALL_ANSWERING = 4;
|
||||||
|
|
||||||
|
/** When an INVITE request is sent. */
|
||||||
|
public static final int OUTGOING_CALL = 5;
|
||||||
|
|
||||||
|
/** When a RINGING response is received for the INVITE request sent. */
|
||||||
|
public static final int OUTGOING_CALL_RING_BACK = 6;
|
||||||
|
|
||||||
|
/** When a CANCEL request is sent for the INVITE request sent. */
|
||||||
|
public static final int OUTGOING_CALL_CANCELING = 7;
|
||||||
|
|
||||||
|
/** When a call is established. */
|
||||||
|
public static final int IN_CALL = 8;
|
||||||
|
|
||||||
|
/** When an OPTIONS request is sent. */
|
||||||
|
public static final int PINGING = 9;
|
||||||
|
|
||||||
|
/** Not defined. */
|
||||||
|
public static final int NOT_DEFINED = 101;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the state to string.
|
||||||
|
*/
|
||||||
|
public static String toString(int state) {
|
||||||
|
switch (state) {
|
||||||
|
case READY_TO_CALL:
|
||||||
|
return "READY_TO_CALL";
|
||||||
|
case REGISTERING:
|
||||||
|
return "REGISTERING";
|
||||||
|
case DEREGISTERING:
|
||||||
|
return "DEREGISTERING";
|
||||||
|
case INCOMING_CALL:
|
||||||
|
return "INCOMING_CALL";
|
||||||
|
case INCOMING_CALL_ANSWERING:
|
||||||
|
return "INCOMING_CALL_ANSWERING";
|
||||||
|
case OUTGOING_CALL:
|
||||||
|
return "OUTGOING_CALL";
|
||||||
|
case OUTGOING_CALL_RING_BACK:
|
||||||
|
return "OUTGOING_CALL_RING_BACK";
|
||||||
|
case OUTGOING_CALL_CANCELING:
|
||||||
|
return "OUTGOING_CALL_CANCELING";
|
||||||
|
case IN_CALL:
|
||||||
|
return "IN_CALL";
|
||||||
|
case PINGING:
|
||||||
|
return "PINGING";
|
||||||
|
default:
|
||||||
|
return "NOT_DEFINED";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private State() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener class that listens to {@link SipSession} events.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static class Listener {
|
||||||
|
/**
|
||||||
|
* Called when an INVITE request is sent to initiate a new call.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
*/
|
||||||
|
public void onCalling(SipSession session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an INVITE request is received.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
* @param caller the SIP profile of the caller
|
||||||
|
* @param sessionDescription the caller's session description
|
||||||
|
*/
|
||||||
|
public void onRinging(SipSession session, SipProfile caller,
|
||||||
|
String sessionDescription) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a RINGING response is received for the INVITE request sent
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
*/
|
||||||
|
public void onRingingBack(SipSession session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the session is established.
|
||||||
|
*
|
||||||
|
* @param session the session object that is associated with the dialog
|
||||||
|
* @param sessionDescription the peer's session description
|
||||||
|
*/
|
||||||
|
public void onCallEstablished(SipSession session,
|
||||||
|
String sessionDescription) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the session is terminated.
|
||||||
|
*
|
||||||
|
* @param session the session object that is associated with the dialog
|
||||||
|
*/
|
||||||
|
public void onCallEnded(SipSession session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the peer is busy during session initialization.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
*/
|
||||||
|
public void onCallBusy(SipSession session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an error occurs during session initialization and
|
||||||
|
* termination.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
* @param errorCode error code defined in {@link SipErrorCode}
|
||||||
|
* @param errorMessage error message
|
||||||
|
*/
|
||||||
|
public void onError(SipSession session, int errorCode,
|
||||||
|
String errorMessage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an error occurs during session modification negotiation.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
* @param errorCode error code defined in {@link SipErrorCode}
|
||||||
|
* @param errorMessage error message
|
||||||
|
*/
|
||||||
|
public void onCallChangeFailed(SipSession session, int errorCode,
|
||||||
|
String errorMessage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a registration request is sent.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
*/
|
||||||
|
public void onRegistering(SipSession session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when registration is successfully done.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
* @param duration duration in second before the registration expires
|
||||||
|
*/
|
||||||
|
public void onRegistrationDone(SipSession session, int duration) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the registration fails.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
* @param errorCode error code defined in {@link SipErrorCode}
|
||||||
|
* @param errorMessage error message
|
||||||
|
*/
|
||||||
|
public void onRegistrationFailed(SipSession session, int errorCode,
|
||||||
|
String errorMessage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the registration gets timed out.
|
||||||
|
*
|
||||||
|
* @param session the session object that carries out the transaction
|
||||||
|
*/
|
||||||
|
public void onRegistrationTimeout(SipSession session) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ISipSession mSession;
|
||||||
|
private Listener mListener;
|
||||||
|
|
||||||
|
SipSession(ISipSession realSession) {
|
||||||
|
mSession = realSession;
|
||||||
|
if (realSession != null) {
|
||||||
|
try {
|
||||||
|
realSession.setListener(createListener());
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "SipSession.setListener(): " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SipSession(ISipSession realSession, Listener listener) {
|
||||||
|
this(realSession);
|
||||||
|
setListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the IP address of the local host on which this SIP session runs.
|
||||||
|
*
|
||||||
|
* @return the IP address of the local host
|
||||||
|
*/
|
||||||
|
public String getLocalIp() {
|
||||||
|
try {
|
||||||
|
return mSession.getLocalIp();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "getLocalIp(): " + e);
|
||||||
|
return "127.0.0.1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the SIP profile that this session is associated with.
|
||||||
|
*
|
||||||
|
* @return the SIP profile that this session is associated with
|
||||||
|
*/
|
||||||
|
public SipProfile getLocalProfile() {
|
||||||
|
try {
|
||||||
|
return mSession.getLocalProfile();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "getLocalProfile(): " + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the SIP profile that this session is connected to. Only available
|
||||||
|
* when the session is associated with a SIP dialog.
|
||||||
|
*
|
||||||
|
* @return the SIP profile that this session is connected to
|
||||||
|
*/
|
||||||
|
public SipProfile getPeerProfile() {
|
||||||
|
try {
|
||||||
|
return mSession.getPeerProfile();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "getPeerProfile(): " + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the session state. The value returned must be one of the states in
|
||||||
|
* {@link SipSessionState}.
|
||||||
|
*
|
||||||
|
* @return the session state
|
||||||
|
*/
|
||||||
|
public int getState() {
|
||||||
|
try {
|
||||||
|
return mSession.getState();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "getState(): " + e);
|
||||||
|
return State.NOT_DEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the session is in a call.
|
||||||
|
*
|
||||||
|
* @return true if the session is in a call
|
||||||
|
*/
|
||||||
|
public boolean isInCall() {
|
||||||
|
try {
|
||||||
|
return mSession.isInCall();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "isInCall(): " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the call ID of the session.
|
||||||
|
*
|
||||||
|
* @return the call ID
|
||||||
|
*/
|
||||||
|
public String getCallId() {
|
||||||
|
try {
|
||||||
|
return mSession.getCallId();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "getCallId(): " + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the listener to listen to the session events. A {@code SipSession}
|
||||||
|
* can only hold one listener at a time. Subsequent calls to this method
|
||||||
|
* override the previous listener.
|
||||||
|
*
|
||||||
|
* @param listener to listen to the session events of this object
|
||||||
|
*/
|
||||||
|
public void setListener(Listener listener) {
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs registration to the server specified by the associated local
|
||||||
|
* profile. The session listener is called back upon success or failure of
|
||||||
|
* registration. The method is only valid to call when the session state is
|
||||||
|
* in {@link SipSessionState#READY_TO_CALL}.
|
||||||
|
*
|
||||||
|
* @param duration duration in second before the registration expires
|
||||||
|
* @see Listener
|
||||||
|
*/
|
||||||
|
public void register(int duration) {
|
||||||
|
try {
|
||||||
|
mSession.register(duration);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "register(): " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs unregistration to the server specified by the associated local
|
||||||
|
* profile. Unregistration is technically the same as registration with zero
|
||||||
|
* expiration duration. The session listener is called back upon success or
|
||||||
|
* failure of unregistration. The method is only valid to call when the
|
||||||
|
* session state is in {@link SipSessionState#READY_TO_CALL}.
|
||||||
|
*
|
||||||
|
* @see Listener
|
||||||
|
*/
|
||||||
|
public void unregister() {
|
||||||
|
try {
|
||||||
|
mSession.unregister();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "unregister(): " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates a call to the specified profile. The session listener is called
|
||||||
|
* back upon defined session events. The method is only valid to call when
|
||||||
|
* the session state is in {@link SipSessionState#READY_TO_CALL}.
|
||||||
|
*
|
||||||
|
* @param callee the SIP profile to make the call to
|
||||||
|
* @param sessionDescription the session description of this call
|
||||||
|
* @param timeout the session will be timed out if the call is not
|
||||||
|
* established within {@code timeout} seconds. Default value (defined
|
||||||
|
* by SIP protocol) is used if {@code timeout} is zero or negative.
|
||||||
|
* @see Listener
|
||||||
|
*/
|
||||||
|
public void makeCall(SipProfile callee, String sessionDescription,
|
||||||
|
int timeout) {
|
||||||
|
try {
|
||||||
|
mSession.makeCall(callee, sessionDescription, timeout);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "makeCall(): " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answers an incoming call with the specified session description. The
|
||||||
|
* method is only valid to call when the session state is in
|
||||||
|
* {@link SipSessionState#INCOMING_CALL}.
|
||||||
|
*
|
||||||
|
* @param sessionDescription the session description to answer this call
|
||||||
|
* @param timeout the session will be timed out if the call is not
|
||||||
|
* established within {@code timeout} seconds. Default value (defined
|
||||||
|
* by SIP protocol) is used if {@code timeout} is zero or negative.
|
||||||
|
*/
|
||||||
|
public void answerCall(String sessionDescription, int timeout) {
|
||||||
|
try {
|
||||||
|
mSession.answerCall(sessionDescription, timeout);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "answerCall(): " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends an established call, terminates an outgoing call or rejects an
|
||||||
|
* incoming call. The method is only valid to call when the session state is
|
||||||
|
* in {@link SipSessionState#IN_CALL},
|
||||||
|
* {@link SipSessionState#INCOMING_CALL},
|
||||||
|
* {@link SipSessionState#OUTGOING_CALL} or
|
||||||
|
* {@link SipSessionState#OUTGOING_CALL_RING_BACK}.
|
||||||
|
*/
|
||||||
|
public void endCall() {
|
||||||
|
try {
|
||||||
|
mSession.endCall();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "endCall(): " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the session description during a call. The method is only valid
|
||||||
|
* to call when the session state is in {@link SipSessionState#IN_CALL}.
|
||||||
|
*
|
||||||
|
* @param sessionDescription the new session description
|
||||||
|
* @param timeout the session will be timed out if the call is not
|
||||||
|
* established within {@code timeout} seconds. Default value (defined
|
||||||
|
* by SIP protocol) is used if {@code timeout} is zero or negative.
|
||||||
|
*/
|
||||||
|
public void changeCall(String sessionDescription, int timeout) {
|
||||||
|
try {
|
||||||
|
mSession.changeCall(sessionDescription, timeout);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "changeCall(): " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ISipSession getRealSession() {
|
||||||
|
return mSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ISipSessionListener createListener() {
|
||||||
|
return new ISipSessionListener.Stub() {
|
||||||
|
public void onCalling(ISipSession session) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onCalling(SipSession.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRinging(ISipSession session, SipProfile caller,
|
||||||
|
String sessionDescription) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onRinging(SipSession.this, caller,
|
||||||
|
sessionDescription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRingingBack(ISipSession session) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onRingingBack(SipSession.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCallEstablished(ISipSession session,
|
||||||
|
String sessionDescription) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onCallEstablished(SipSession.this,
|
||||||
|
sessionDescription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCallEnded(ISipSession session) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onCallEnded(SipSession.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCallBusy(ISipSession session) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onCallBusy(SipSession.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCallChangeFailed(ISipSession session, int errorCode,
|
||||||
|
String message) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onCallChangeFailed(SipSession.this, errorCode,
|
||||||
|
message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onError(ISipSession session, int errorCode, String message) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onError(SipSession.this, errorCode, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRegistering(ISipSession session) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onRegistering(SipSession.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRegistrationDone(ISipSession session, int duration) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onRegistrationDone(SipSession.this, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRegistrationFailed(ISipSession session, int errorCode,
|
||||||
|
String message) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onRegistrationFailed(SipSession.this, errorCode,
|
||||||
|
message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRegistrationTimeout(ISipSession session) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onRegistrationTimeout(SipSession.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,94 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.net.sip;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines {@link ISipSession} states.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public class SipSessionState {
|
|
||||||
/** When session is ready to initiate a call or transaction. */
|
|
||||||
public static final int READY_TO_CALL = 0;
|
|
||||||
|
|
||||||
/** When the registration request is sent out. */
|
|
||||||
public static final int REGISTERING = 1;
|
|
||||||
|
|
||||||
/** When the unregistration request is sent out. */
|
|
||||||
public static final int DEREGISTERING = 2;
|
|
||||||
|
|
||||||
/** When an INVITE request is received. */
|
|
||||||
public static final int INCOMING_CALL = 3;
|
|
||||||
|
|
||||||
/** When an OK response is sent for the INVITE request received. */
|
|
||||||
public static final int INCOMING_CALL_ANSWERING = 4;
|
|
||||||
|
|
||||||
/** When an INVITE request is sent. */
|
|
||||||
public static final int OUTGOING_CALL = 5;
|
|
||||||
|
|
||||||
/** When a RINGING response is received for the INVITE request sent. */
|
|
||||||
public static final int OUTGOING_CALL_RING_BACK = 6;
|
|
||||||
|
|
||||||
/** When a CANCEL request is sent for the INVITE request sent. */
|
|
||||||
public static final int OUTGOING_CALL_CANCELING = 7;
|
|
||||||
|
|
||||||
/** When a call is established. */
|
|
||||||
public static final int IN_CALL = 8;
|
|
||||||
|
|
||||||
/** Some error occurs when making a remote call to {@link ISipSession}. */
|
|
||||||
public static final int REMOTE_ERROR = 9;
|
|
||||||
|
|
||||||
/** When an OPTIONS request is sent. */
|
|
||||||
public static final int PINGING = 10;
|
|
||||||
|
|
||||||
/** Not defined. */
|
|
||||||
public static final int NOT_DEFINED = 101;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the state to string.
|
|
||||||
*/
|
|
||||||
public static String toString(int state) {
|
|
||||||
switch (state) {
|
|
||||||
case READY_TO_CALL:
|
|
||||||
return "READY_TO_CALL";
|
|
||||||
case REGISTERING:
|
|
||||||
return "REGISTERING";
|
|
||||||
case DEREGISTERING:
|
|
||||||
return "DEREGISTERING";
|
|
||||||
case INCOMING_CALL:
|
|
||||||
return "INCOMING_CALL";
|
|
||||||
case INCOMING_CALL_ANSWERING:
|
|
||||||
return "INCOMING_CALL_ANSWERING";
|
|
||||||
case OUTGOING_CALL:
|
|
||||||
return "OUTGOING_CALL";
|
|
||||||
case OUTGOING_CALL_RING_BACK:
|
|
||||||
return "OUTGOING_CALL_RING_BACK";
|
|
||||||
case OUTGOING_CALL_CANCELING:
|
|
||||||
return "OUTGOING_CALL_CANCELING";
|
|
||||||
case IN_CALL:
|
|
||||||
return "IN_CALL";
|
|
||||||
case REMOTE_ERROR:
|
|
||||||
return "REMOTE_ERROR";
|
|
||||||
case PINGING:
|
|
||||||
return "PINGING";
|
|
||||||
default:
|
|
||||||
return "NOT_DEFINED";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SipSessionState() {
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user