diff options
author | Chung-yih Wang <cywang@google.com> | 2010-10-07 07:09:58 +0800 |
---|---|---|
committer | Chung-yih Wang <cywang@google.com> | 2010-10-07 07:09:58 +0800 |
commit | 5a336104824785dcae1e7aaa67d49fa051857ec9 (patch) | |
tree | d3868e9c5794dd936116e9d02c36448bbba21ec9 | |
parent | 68513a76ebaacdff0a837ae2e743078f2bb5d514 (diff) | |
download | android_external_nist-sip-5a336104824785dcae1e7aaa67d49fa051857ec9.tar.gz android_external_nist-sip-5a336104824785dcae1e7aaa67d49fa051857ec9.tar.bz2 android_external_nist-sip-5a336104824785dcae1e7aaa67d49fa051857ec9.zip |
Removed unused files. Do not merge.
Change-Id: I73c451b2cff2a683fefc581e2b63b502e11b764d
19 files changed, 0 insertions, 4042 deletions
diff --git a/src/android/net/sip/SdpSessionDescription.java b/src/android/net/sip/SdpSessionDescription.java deleted file mode 100644 index 8ce2ddd..0000000 --- a/src/android/net/sip/SdpSessionDescription.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2009 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 gov.nist.javax.sdp.SessionDescriptionImpl; -import gov.nist.javax.sdp.fields.AttributeField; -import gov.nist.javax.sdp.fields.ConnectionField; -import gov.nist.javax.sdp.fields.MediaField; -import gov.nist.javax.sdp.fields.OriginField; -import gov.nist.javax.sdp.fields.ProtoVersionField; -import gov.nist.javax.sdp.fields.SessionNameField; -import gov.nist.javax.sdp.fields.TimeField; -import gov.nist.javax.sdp.parser.SDPAnnounceParser; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Vector; -import javax.sdp.Connection; -import javax.sdp.MediaDescription; -import javax.sdp.SdpException; -import javax.sip.SipException; - -public class SdpSessionDescription implements SessionDescription { - private SessionDescriptionImpl mSessionDescription; - private String mPeerMediaAddress; - private int mPeerMediaPort; - - public static class Builder { - private SdpSessionDescription mSdp = new SdpSessionDescription(); - private SessionDescriptionImpl mSessionDescription; - - public Builder(String sessionName) throws SdpException { - mSessionDescription = new SessionDescriptionImpl(); - try { - ProtoVersionField proto = new ProtoVersionField(); - proto.setVersion(0); - mSessionDescription.addField(proto); - - TimeField time = new TimeField(); - time.setZero(); - mSessionDescription.addField(time); - - SessionNameField session = new SessionNameField(); - session.setValue(sessionName); - mSessionDescription.addField(session); - } catch (Exception e) { - throw new SdpException(e.toString(), e); - } - } - - public Builder setConnectionInfo(String netType, String addrType, - String addr) throws SdpException { - try { - ConnectionField connection = new ConnectionField(); - connection.setNetworkType(netType); - connection.setAddressType(addrType); - connection.setAddress(addr); - mSessionDescription.addField(connection); - } catch (Exception e) { - throw new SdpException(e.toString(), e); - } - return this; - } - - public Builder setOrigin(SipProfile user, long sessionId, - long sessionVersion, String networkType, String addressType, - String address) throws SdpException { - try { - OriginField origin = new OriginField(); - origin.setUsername(user.getUserName()); - origin.setSessionId(sessionId); - origin.setSessionVersion(sessionVersion); - origin.setAddressType(addressType); - origin.setNetworkType(networkType); - origin.setAddress(address); - mSessionDescription.addField(origin); - } catch (Exception e) { - throw new SdpException(e.toString(), e); - } - return this; - } - - public Builder addMedia(String media, int port, int numPorts, - String transport, Integer... types) throws SdpException { - MediaField field = new MediaField(); - Vector<Integer> typeVector = new Vector<Integer>(); - Collections.addAll(typeVector, types); - try { - field.setMediaType(media); - field.setMediaPort(port); - field.setPortCount(numPorts); - field.setProtocol(transport); - field.setMediaFormats(typeVector); - mSessionDescription.addField(field); - } catch (Exception e) { - throw new SdpException(e.toString(), e); - } - return this; - } - - public Builder addMediaAttribute(String name, String value) - throws SdpException { - try { - if (mSessionDescription.getMediaDescriptions(false) == null) { - throw new SdpException("Should add media first!"); - } - AttributeField attribute = new AttributeField(); - attribute.setName(name); - attribute.setValueAllowNull(value); - mSessionDescription.addField(attribute); - } catch (Exception e) { - throw new SdpException(e.toString(), e); - } - return this; - } - - public Builder addSessionAttribute(String name, String value) - throws SdpException { - try { - AttributeField attribute = new AttributeField(); - attribute.setName(name); - attribute.setValueAllowNull(value); - mSessionDescription.addField(attribute); - } catch (Exception e) { - throw new SdpException(e.toString(), e); - } - return this; - } - - public SdpSessionDescription build() { - mSdp.mSessionDescription = mSessionDescription; - return mSdp; - } - } - - private SdpSessionDescription() { - } - - public SdpSessionDescription(String sdpString) throws SdpException { - try { - mSessionDescription = new SDPAnnounceParser(sdpString).parse(); - } catch (ParseException e) { - throw new SdpException(e.toString(), e); - } - init(); - } - - public SdpSessionDescription(byte[] content) throws SdpException { - this(new String(content)); - } - - public String getPeerMediaAddress() { - return mPeerMediaAddress; - } - - public int getPeerMediaPort() { - return mPeerMediaPort; - } - - public List<Integer> getMediaFormats() { - MediaDescription md = getMediaDescription(); - Vector<String> formatVector = (md == null) - ? null - : md.getMedia().getFormats(); - if (formatVector == null) formatVector = new Vector<String>(); - List<Integer> formats = new ArrayList<Integer>(); - for (String id : formatVector) { - try { - formats.add(Integer.parseInt(id)); - } catch (NumberFormatException e) { - // ignore - } - } - return formats; - } - - private void init() throws SdpException { - MediaDescription md = getMediaDescription(); - mPeerMediaPort = md.getMedia().getMediaPort(); - - Connection connection = md.getConnection(); - if (connection == null) { - connection = mSessionDescription.getConnection(); - } - mPeerMediaAddress = connection.getAddress(); - } - - private MediaDescription getMediaDescription() { - try { - Vector vector = mSessionDescription.getMediaDescriptions(false); - // FIXME: how to handle multiple media descriptions - return (MediaDescription) vector.firstElement(); - } catch (SdpException e) { - android.util.Log.e("SdpSessionDescription", e.toString()); - return null; - } - } - - public String getType() { - return "sdp"; - } - - public byte[] getContent() { - return mSessionDescription.toString().getBytes(); - } - - public String toString() { - return mSessionDescription.toString(); - } -} diff --git a/src/android/net/sip/SessionDescription.java b/src/android/net/sip/SessionDescription.java deleted file mode 100644 index c43e0c7..0000000 --- a/src/android/net/sip/SessionDescription.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2009 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 javax.sip.SipException; - -public interface SessionDescription { - String getType(); - byte[] getContent(); -} diff --git a/src/android/net/sip/SipHelper.java b/src/android/net/sip/SipHelper.java deleted file mode 100644 index 606b2fc..0000000 --- a/src/android/net/sip/SipHelper.java +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) 2009 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.util.Log; - -import gov.nist.javax.sip.SipStackExt; -import gov.nist.javax.sip.clientauthutils.AccountManager; -import gov.nist.javax.sip.clientauthutils.AuthenticationHelper; -import gov.nist.javax.sip.clientauthutils.UserCredentials; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.EventObject; -import java.util.List; -import javax.sip.ClientTransaction; -import javax.sip.Dialog; -import javax.sip.DialogTerminatedEvent; -import javax.sip.InvalidArgumentException; -import javax.sip.ListeningPoint; -import javax.sip.PeerUnavailableException; -import javax.sip.RequestEvent; -import javax.sip.ResponseEvent; -import javax.sip.ServerTransaction; -import javax.sip.SipException; -import javax.sip.SipFactory; -import javax.sip.SipProvider; -import javax.sip.SipStack; -import javax.sip.Transaction; -import javax.sip.TransactionAlreadyExistsException; -import javax.sip.TransactionTerminatedEvent; -import javax.sip.TransactionUnavailableException; -import javax.sip.TransactionState; -import javax.sip.address.Address; -import javax.sip.address.AddressFactory; -import javax.sip.address.SipURI; -import javax.sip.header.CSeqHeader; -import javax.sip.header.CallIdHeader; -import javax.sip.header.ContactHeader; -import javax.sip.header.FromHeader; -import javax.sip.header.Header; -import javax.sip.header.HeaderFactory; -import javax.sip.header.MaxForwardsHeader; -import javax.sip.header.ToHeader; -import javax.sip.header.ViaHeader; -import javax.sip.message.Message; -import javax.sip.message.MessageFactory; -import javax.sip.message.Request; -import javax.sip.message.Response; - -/** - * Helper class for holding SIP stack related classes and for various low-level - * SIP tasks like sending messages. - */ -class SipHelper { - private static final String TAG = SipHelper.class.getSimpleName(); - - private SipStack mSipStack; - private SipProvider mSipProvider; - private AddressFactory mAddressFactory; - private HeaderFactory mHeaderFactory; - private MessageFactory mMessageFactory; - - public SipHelper(SipStack sipStack, SipProvider sipProvider) - throws PeerUnavailableException { - mSipStack = sipStack; - mSipProvider = sipProvider; - - SipFactory sipFactory = SipFactory.getInstance(); - mAddressFactory = sipFactory.createAddressFactory(); - mHeaderFactory = sipFactory.createHeaderFactory(); - mMessageFactory = sipFactory.createMessageFactory(); - } - - private FromHeader createFromHeader(SipProfile profile, String tag) - throws ParseException { - return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag); - } - - private ToHeader createToHeader(SipProfile profile) throws ParseException { - return createToHeader(profile, null); - } - - private ToHeader createToHeader(SipProfile profile, String tag) - throws ParseException { - return mHeaderFactory.createToHeader(profile.getSipAddress(), tag); - } - - private CallIdHeader createCallIdHeader() { - return mSipProvider.getNewCallId(); - } - - private CSeqHeader createCSeqHeader(String method) - throws ParseException, InvalidArgumentException { - long sequence = (long) (Math.random() * 10000); - return mHeaderFactory.createCSeqHeader(sequence, method); - } - - private MaxForwardsHeader createMaxForwardsHeader() - throws InvalidArgumentException { - return mHeaderFactory.createMaxForwardsHeader(70); - } - - private MaxForwardsHeader createMaxForwardsHeader(int max) - throws InvalidArgumentException { - return mHeaderFactory.createMaxForwardsHeader(max); - } - - private List<ViaHeader> createViaHeaders() - throws ParseException, InvalidArgumentException { - List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1); - ListeningPoint lp = mSipProvider.getListeningPoint("udp"); - viaHeaders.add(mHeaderFactory.createViaHeader(lp.getIPAddress(), - lp.getPort(), "udp", null)); - return viaHeaders; - } - - private ContactHeader createContactHeader(SipProfile profile) - throws ParseException, InvalidArgumentException { - ListeningPoint lp = mSipProvider.getListeningPoint("udp"); - SipURI contactURI = createSipUri(profile.getUserName(), lp); - - Address contactAddress = mAddressFactory.createAddress(contactURI); - contactAddress.setDisplayName(profile.getDisplayName()); - - return mHeaderFactory.createContactHeader(contactAddress); - } - - private SipURI createSipUri(String username, ListeningPoint lp) - throws ParseException { - SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress()); - try { - uri.setPort(lp.getPort()); - } catch (InvalidArgumentException e) { - throw new RuntimeException(e); - } - return uri; - } - - public ClientTransaction sendRegister(SipProfile userProfile, String tag, - int expiry) throws SipException { - try { - FromHeader fromHeader = createFromHeader(userProfile, tag); - ToHeader toHeader = createToHeader(userProfile); - SipURI requestURI = mAddressFactory.createSipURI("sip:" - + userProfile.getSipDomain()); - List<ViaHeader> viaHeaders = createViaHeaders(); - CallIdHeader callIdHeader = createCallIdHeader(); - CSeqHeader cSeqHeader = createCSeqHeader(Request.REGISTER); - MaxForwardsHeader maxForwards = createMaxForwardsHeader(); - - Request request = mMessageFactory.createRequest(requestURI, - Request.REGISTER, callIdHeader, cSeqHeader, fromHeader, - toHeader, viaHeaders, maxForwards); - - request.addHeader(createContactHeader(userProfile)); - request.addHeader(mHeaderFactory.createExpiresHeader(expiry)); - Header userAgentHeader = mHeaderFactory.createHeader("User-Agent", - "AndroidSip/0.1.001"); - request.addHeader(userAgentHeader); - - ClientTransaction clientTransaction = - mSipProvider.getNewClientTransaction(request); - clientTransaction.sendRequest(); - return clientTransaction; - } catch (ParseException e) { - throw new SipException("sendRegister()", e); - } - } - - public ClientTransaction handleChallenge(ResponseEvent responseEvent, - final SipProfile userProfile) throws SipException { - AccountManager accountManager = new AccountManager() { - public UserCredentials getCredentials(ClientTransaction - challengedTransaction, String realm) { - return userProfile; - } - }; - AuthenticationHelper authenticationHelper = - ((SipStackExt) mSipStack).getAuthenticationHelper( - accountManager, mHeaderFactory); - ClientTransaction tid = responseEvent.getClientTransaction(); - ClientTransaction ct = authenticationHelper.handleChallenge( - responseEvent.getResponse(), tid, mSipProvider, 5); - ct.sendRequest(); - return ct; - } - - public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, - SessionDescription sessionDescription, String tag) - throws SipException { - try { - FromHeader fromHeader = createFromHeader(caller, tag); - ToHeader toHeader = createToHeader(callee); - SipURI requestURI = callee.getUri(); - List<ViaHeader> viaHeaders = createViaHeaders(); - CallIdHeader callIdHeader = createCallIdHeader(); - CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE); - MaxForwardsHeader maxForwards = createMaxForwardsHeader(); - - Request request = mMessageFactory.createRequest(requestURI, - Request.INVITE, callIdHeader, cSeqHeader, fromHeader, - toHeader, viaHeaders, maxForwards); - - request.addHeader(createContactHeader(caller)); - request.setContent(sessionDescription.getContent(), - mHeaderFactory.createContentTypeHeader( - "application", sessionDescription.getType())); - - ClientTransaction clientTransaction = - mSipProvider.getNewClientTransaction(request); - clientTransaction.sendRequest(); - return clientTransaction; - } catch (ParseException e) { - throw new SipException("sendInvite()", e); - } - } - - public ClientTransaction sendReinvite(Dialog dialog, - SessionDescription sessionDescription) throws SipException { - try { - dialog.incrementLocalSequenceNumber(); - Request request = dialog.createRequest(Request.INVITE); - request.setContent(sessionDescription.getContent(), - mHeaderFactory.createContentTypeHeader( - "application", sessionDescription.getType())); - - ClientTransaction clientTransaction = - mSipProvider.getNewClientTransaction(request); - clientTransaction.sendRequest(); - return clientTransaction; - } catch (ParseException e) { - throw new SipException("sendReinvite()", e); - } - } - - private ServerTransaction getServerTransaction(RequestEvent event) - throws SipException { - ServerTransaction transaction = event.getServerTransaction(); - if (transaction == null) { - Request request = event.getRequest(); - return mSipProvider.getNewServerTransaction(request); - } else { - return transaction; - } - } - - /** - * @param event the INVITE request event - */ - public ServerTransaction sendRinging(RequestEvent event) - throws SipException { - try { - Request request = event.getRequest(); - ServerTransaction transaction = getServerTransaction(event); - transaction.sendResponse( - mMessageFactory.createResponse(Response.RINGING, request)); - return transaction; - } catch (ParseException e) { - throw new SipException("sendRinging()", e); - } - } - - /** - * @param event the INVITE request event - */ - public void sendInviteOk(RequestEvent event, SipProfile localProfile, - SessionDescription sessionDescription, String tag, - ServerTransaction inviteTransaction) throws SipException { - try { - Request request = event.getRequest(); - Response response = mMessageFactory.createResponse(Response.OK, - request); - response.addHeader(createContactHeader(localProfile)); - ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); - toHeader.setTag(tag); - response.addHeader(toHeader); - - response.setContent(sessionDescription.getContent(), - mHeaderFactory.createContentTypeHeader( - "application", sessionDescription.getType())); - - if (inviteTransaction.getState() != TransactionState.COMPLETED) { - inviteTransaction.sendResponse(response); - } - } catch (ParseException e) { - throw new SipException("sendInviteOk()", e); - } - } - - /** - * @param event the INVITE request event - */ - public void sendReInviteOk(RequestEvent event, SipProfile localProfile) - throws SipException { - try { - Request request = event.getRequest(); - Response response = mMessageFactory.createResponse(Response.OK, - request); - response.addHeader(createContactHeader(localProfile)); - ServerTransaction transaction = event.getServerTransaction(); - - if (transaction.getState() != TransactionState.COMPLETED) { - transaction.sendResponse(response); - } - } catch (ParseException e) { - throw new SipException("sendReInviteOk()", e); - } - } - - public void sendInviteBusyHere(RequestEvent event, - ServerTransaction inviteTransaction) throws SipException { - try { - Request request = event.getRequest(); - Response response = mMessageFactory.createResponse( - Response.BUSY_HERE, request); - - if (inviteTransaction.getState() != TransactionState.COMPLETED) { - inviteTransaction.sendResponse(response); - } - } catch (ParseException e) { - throw new SipException("sendInviteBusyHere()", e); - } - } - - /** - * @param event the INVITE ACK request event - */ - public void sendInviteAck(ResponseEvent event, Dialog dialog) - throws SipException { - Response response = event.getResponse(); - long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)) - .getSeqNumber(); - dialog.sendAck(dialog.createAck(cseq)); - } - - public void sendBye(Dialog dialog) throws SipException { - // SipStack increases seq number automatically - // so no need to call dialog.incrementLocalSequenceNumber(). - Request byeRequest = dialog.createRequest(Request.BYE); - Log.v(TAG, "send BYE: " + byeRequest); - dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); - } - - public void sendCancel(ClientTransaction inviteTransaction) - throws SipException { - // The cancel request must use the same CSeq as the request to cancel - // so no need to call dialog.incrementLocalSequenceNumber(). - Request cancelRequest = inviteTransaction.createCancel(); - mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); - } - - public void sendResponse(RequestEvent event, int responseCode) - throws SipException { - try { - getServerTransaction(event).sendResponse( - mMessageFactory.createResponse( - responseCode, event.getRequest())); - } catch (ParseException e) { - throw new SipException("sendResponse()", e); - } - } - - public void sendInviteRequestTerminated(Request inviteRequest, - ServerTransaction inviteTransaction) throws SipException { - try { - inviteTransaction.sendResponse(mMessageFactory.createResponse( - Response.REQUEST_TERMINATED, inviteRequest)); - } catch (ParseException e) { - throw new SipException("sendInviteRequestTerminated()", e); - } - } - - public String getSessionKey(SipSession session) { - Dialog dialog = session.getDialog(); - return (dialog == null - ? getSessionKey(session.getLocalProfile().getSipAddress()) - : dialog.getCallId().getCallId()); - } - - public static String[] getPossibleSessionKeys(EventObject event) { - if (event instanceof RequestEvent) { - return getPossibleSessionKeys(((RequestEvent) event).getRequest()); - } else if (event instanceof ResponseEvent) { - return getPossibleSessionKeys( - ((ResponseEvent) event).getResponse()); - } else if (event instanceof DialogTerminatedEvent) { - Dialog dialog = ((DialogTerminatedEvent) event).getDialog(); - return getPossibleSessionKeys( - ((DialogTerminatedEvent) event).getDialog()); - } else if (event instanceof TransactionTerminatedEvent) { - TransactionTerminatedEvent e = (TransactionTerminatedEvent) event; - return getPossibleSessionKeys(e.isServerTransaction() - ? e.getServerTransaction() - : e.getClientTransaction()); - } else { - Object source = event.getSource(); - if (source instanceof Transaction) { - return getPossibleSessionKeys(((Transaction) source)); - } else if (source instanceof Dialog) { - return getPossibleSessionKeys((Dialog) source); - } - } - return new String[0]; - } - - private static String[] getPossibleSessionKeys(Message message) { - CallIdHeader callIdHeader = - (CallIdHeader) message.getHeader(CallIdHeader.NAME); - FromHeader fromHeader = (FromHeader) message.getHeader(FromHeader.NAME); - ToHeader toHeader = (ToHeader) message.getHeader(ToHeader.NAME); - return new String[] {callIdHeader.getCallId(), - getSessionKey(fromHeader.getAddress()), - getSessionKey(toHeader.getAddress())}; - } - - private static String[] getPossibleSessionKeys(Transaction transaction) { - return getPossibleSessionKeys(transaction.getRequest()); - } - - private static String[] getPossibleSessionKeys(Dialog dialog) { - return new String[] {dialog.getCallId().getCallId()}; - } - - private static String getSessionKey(Address address) { - Address clonedAddress = (Address) address.clone(); - // remove all optional fields - try { - clonedAddress.setDisplayName(null); - ((SipURI) clonedAddress.getURI()).setUserPassword(null); - } catch (ParseException e) { - // ignored - } - return clonedAddress.toString(); - } -} diff --git a/src/android/net/sip/SipProfile.java b/src/android/net/sip/SipProfile.java deleted file mode 100644 index 039c850..0000000 --- a/src/android/net/sip/SipProfile.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2009 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 gov.nist.javax.sip.clientauthutils.UserCredentials; - -import android.text.TextUtils; - -import java.text.ParseException; -import javax.sip.InvalidArgumentException; -import javax.sip.ListeningPoint; -import javax.sip.PeerUnavailableException; -import javax.sip.SipFactory; -import javax.sip.address.Address; -import javax.sip.address.AddressFactory; -import javax.sip.address.SipURI; -import javax.sip.address.URI; - -/** - */ -public class SipProfile implements UserCredentials { - private Address mAddress; - private String mOutboundProxy; - private String mPassword; - private String mDomain; - private String mProtocol = ListeningPoint.UDP; - - public static class Builder { - private AddressFactory mAddressFactory; - private SipProfile mProfile = new SipProfile(); - private SipURI mUri; - private String mDisplayName; - private String mOutboundProxy; - - { - try { - mAddressFactory = - SipFactory.getInstance().createAddressFactory(); - } catch (PeerUnavailableException e) { - throw new RuntimeException(e); - } - } - - public Builder(String uriString) throws ParseException { - URI uri = mAddressFactory.createURI(fix(uriString)); - if (uri instanceof SipURI) { - mUri = (SipURI) uri; - } else { - throw new ParseException(uriString + " is not a SIP URI", 0); - } - mProfile.mDomain = mUri.getHost(); - } - - public Builder(String username, String serverAddress) - throws ParseException { - mUri = mAddressFactory.createSipURI(username, serverAddress); - mProfile.mDomain = serverAddress; - } - - private String fix(String uriString) { - return (uriString.trim().toLowerCase().startsWith("sip:") - ? uriString - : "sip:" + uriString); - } - - public Builder setPassword(String password) { - mUri.setUserPassword(password); - return this; - } - - public Builder setPort(int port) throws InvalidArgumentException { - mUri.setPort(port); - return this; - } - - public Builder setDomain(String domain) { - mProfile.mDomain = domain; - return this; - } - - public Builder setProtocol(String protocol) { - // TODO: verify - mProfile.mProtocol = protocol; - return this; - } - - public Builder setOutboundProxy(String outboundProxy) { - mOutboundProxy = outboundProxy; - return this; - } - - public Builder setDisplayName(String displayName) throws ParseException { - mDisplayName = displayName; - return this; - } - - public SipProfile build() { - // remove password from URI - mProfile.mPassword = mUri.getUserPassword(); - mUri.setUserPassword(null); - try { - mProfile.mAddress = mAddressFactory.createAddress( - mDisplayName, mUri); - if (!TextUtils.isEmpty(mOutboundProxy)) { - SipURI uri = (SipURI) - mAddressFactory.createURI(fix(mOutboundProxy)); - mProfile.mOutboundProxy = uri.getHost() + ":" - + uri.getPort() + "/UDP"; - } - } catch (ParseException e) { - // must not occur - throw new RuntimeException(e); - } - return mProfile; - } - } - - private SipProfile() { - } - - public SipURI getUri() { - return (SipURI) mAddress.getURI(); - } - - public Address getSipAddress() { - return mAddress; - } - - public String getDisplayName() { - return mAddress.getDisplayName(); - } - - /* UserCredentials APIs */ - public String getUserName() { - return getUri().getUser(); - } - - public String getPassword() { - return mPassword; - } - - public String getSipDomain() { - return mDomain; - } - - public String getServerAddress() { - return getUri().getHost(); - } - - public int getPort() { - return getUri().getPort(); - } - - public String getProtocol() { - return mProtocol; - } - - public String getOutboundProxy() { - return mOutboundProxy; - } -} diff --git a/src/android/net/sip/SipSession.java b/src/android/net/sip/SipSession.java deleted file mode 100644 index ea243dd..0000000 --- a/src/android/net/sip/SipSession.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2009 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 javax.sip.Dialog; -import javax.sip.SipException; - -public interface SipSession { - SipProfile getLocalProfile(); - SipProfile getPeerProfile(); - SipSessionState getState(); - Dialog getDialog(); - - void makeCall(SipProfile party, SessionDescription sessionDescription) - throws SipException; - void answerCall(SessionDescription sessionDescription) throws SipException; - void endCall() throws SipException; - - void changeCall(SessionDescription sessionDescription) throws SipException; - - void register() throws SipException; - void deRegister() throws SipException; -} diff --git a/src/android/net/sip/SipSessionGroup.java b/src/android/net/sip/SipSessionGroup.java deleted file mode 100644 index 0acd5a6..0000000 --- a/src/android/net/sip/SipSessionGroup.java +++ /dev/null @@ -1,858 +0,0 @@ -/* - * Copyright (C) 2009 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.text.TextUtils; -import android.util.Log; - -import java.io.IOException; -import java.net.DatagramSocket; -import java.text.ParseException; -import java.util.EventObject; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.Timer; -import java.util.TimerTask; -import java.util.TooManyListenersException; - -import javax.sip.ClientTransaction; -import javax.sip.Dialog; -import javax.sip.DialogTerminatedEvent; -import javax.sip.IOExceptionEvent; -import javax.sip.InvalidArgumentException; -import javax.sip.ListeningPoint; -import javax.sip.RequestEvent; -import javax.sip.ResponseEvent; -import javax.sip.ServerTransaction; -import javax.sip.SipException; -import javax.sip.SipFactory; -import javax.sip.SipListener; -import javax.sip.SipProvider; -import javax.sip.SipStack; -import javax.sip.TimeoutEvent; -import javax.sip.Transaction; -import javax.sip.TransactionTerminatedEvent; -import javax.sip.address.Address; -import javax.sip.address.SipURI; -import javax.sip.header.CSeqHeader; -import javax.sip.header.ExpiresHeader; -import javax.sip.header.FromHeader; -import javax.sip.message.Message; -import javax.sip.message.Request; -import javax.sip.message.Response; - -/** - * Manages SipSession's for a SIP account. - */ -class SipSessionGroup implements SipListener { - private static final String TAG = SipSessionGroup.class.getSimpleName(); - private static final int EXPIRY_TIME = 3600; - - private static final EventObject REGISTER = new EventObject("Register"); - private static final EventObject DEREGISTER = new EventObject("Deregister"); - private static final EventObject END_CALL = new EventObject("End call"); - private static final EventObject HOLD_CALL = new EventObject("Hold call"); - private static final EventObject CONTINUE_CALL - = new EventObject("Continue call"); - - private static final String STACK_NAME = "A SIP STACK"; - - private SipStack mSipStack; - private SipHelper mSipHelper; - private SipProfile mLocalProfile; - - // default session that processes all the un-attended messages received - // from the UDP port - private SipSessionImpl mDefaultSession; - - // call-id-to-SipSession map - private Map<String, SipSessionImpl> mSessionMap = - new HashMap<String, SipSessionImpl>(); - - SipSessionGroup(String localIp, SipProfile myself, - SipSessionListener listener) throws SipException { - SipFactory sipFactory = SipFactory.getInstance(); - Properties properties = new Properties(); - properties.setProperty("javax.sip.STACK_NAME", STACK_NAME); - String outboundProxy = myself.getOutboundProxy(); - if (!TextUtils.isEmpty(outboundProxy)) { - properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy); - } - SipStack stack = mSipStack = sipFactory.createSipStack(properties); - stack.start(); - - SipProvider provider = stack.createSipProvider( - stack.createListeningPoint(localIp, allocateLocalPort(), - ListeningPoint.UDP)); - try { - provider.addSipListener(this); - } catch (TooManyListenersException e) { - // must never happen - throw new SipException("SipSessionGroup constructor", e); - } - mSipHelper = new SipHelper(stack, provider); - mLocalProfile = myself; - mDefaultSession = new SipSessionImpl(listener); - } - - public synchronized void close() { - if (mSipStack != null) { - mSipStack.stop(); - mSipStack = null; - } - } - - SipSession getDefaultSession() { - return mDefaultSession; - } - - private static int allocateLocalPort() throws SipException { - try { - DatagramSocket s = new DatagramSocket(); - int localPort = s.getLocalPort(); - s.close(); - return localPort; - } catch (IOException e) { - throw new SipException("allocateLocalPort()", e); - } - } - - private synchronized SipSessionImpl getSipSession(EventObject event) { - String[] keys = mSipHelper.getPossibleSessionKeys(event); - Log.v(TAG, " possible keys " + keys.length + " for evt: " + event); - for (String key : keys) Log.v(TAG, " '" + key + "'"); - Log.v(TAG, " active sessions:"); - for (String k : mSessionMap.keySet()) { - Log.v(TAG, " ----- '" + k + "': " + mSessionMap.get(k)); - } - for (String uri : mSipHelper.getPossibleSessionKeys(event)) { - SipSessionImpl callSession = mSessionMap.get(uri); - // return first match - if (callSession != null) return callSession; - } - return mDefaultSession; - } - - private synchronized void addSipSession(SipSessionImpl newSession) { - String key = mSipHelper.getSessionKey(newSession); - Log.v(TAG, " +++++ add a session with key: '" + key + "'"); - mSessionMap.put(key, newSession); - for (String k : mSessionMap.keySet()) { - Log.v(TAG, " ..... " + k + ": " + mSessionMap.get(k)); - } - } - - private synchronized void removeSipSession(SipSessionImpl session) { - String key = mSipHelper.getSessionKey(session); - SipSessionImpl s = mSessionMap.remove(key); - // sanity check - if ((s != null) && (s != session)) { - Log.w(TAG, "session " + session + " is not associated with key '" - + key + "'"); - mSessionMap.put(key, s); - } else { - Log.v(TAG, " remove session " + session + " with key '" + key - + "'"); - } - for (String k : mSessionMap.keySet()) { - Log.v(TAG, " ..... " + k + ": " + mSessionMap.get(k)); - } - } - - public void processRequest(RequestEvent event) { - process(event); - } - - public void processResponse(ResponseEvent event) { - process(event); - } - - public void processIOException(IOExceptionEvent event) { - // TODO: find proper listener to pass the exception - process(event); - } - - public void processTimeout(TimeoutEvent event) { - // TODO: find proper listener to pass the exception - process(event); - } - - public void processTransactionTerminated(TransactionTerminatedEvent event) { - // TODO: clean up if session is in in-transaction states - //process(event); - } - - public void processDialogTerminated(DialogTerminatedEvent event) { - process(event); - } - - private void process(EventObject event) { - SipSessionImpl session = getSipSession(event); - if (session == null) { - Log.w(TAG, "event not processed: " + event); - return; - } - try { - session.process(event); - } catch (Throwable e) { - Log.e(TAG, "event process error: " + event, e); - session.mListener.onError(session, e); - } - } - - private class SipSessionImpl implements SipSession { - private SipProfile mPeerProfile; - private SipSessionListener mListener; - private SipSessionState mState = SipSessionState.READY_FOR_CALL; - private RequestEvent mInviteReceived; - private Dialog mDialog; - private ServerTransaction mServerTransaction; - private ClientTransaction mClientTransaction; - private byte[] mPeerSessionDescription; - - SipSessionImpl(SipSessionListener listener) { - mListener = listener; - } - - private void reset() { - mPeerProfile = null; - mState = SipSessionState.READY_FOR_CALL; - mInviteReceived = null; - mDialog = null; - mServerTransaction = null; - mClientTransaction = null; - mPeerSessionDescription = null; - } - - public SipProfile getLocalProfile() { - return mLocalProfile; - } - - public synchronized SipProfile getPeerProfile() { - return mPeerProfile; - } - - public synchronized Dialog getDialog() { - return mDialog; - } - - public synchronized SipSessionState getState() { - return mState; - } - - public void makeCall(SipProfile peerProfile, - SessionDescription sessionDescription) throws SipException { - process(new MakeCallCommand(peerProfile, sessionDescription)); - } - - public void answerCall(SessionDescription sessionDescription) - throws SipException { - process(new MakeCallCommand(mPeerProfile, sessionDescription)); - } - - public void endCall() throws SipException { - process(END_CALL); - } - - // http://www.tech-invite.com/Ti-sip-service-1.html#fig5 - public void changeCall(SessionDescription sessionDescription) - throws SipException { - process(new MakeCallCommand(mPeerProfile, sessionDescription)); - } - - public void register() throws SipException { - process(REGISTER); - } - - public void deRegister() throws SipException { - process(DEREGISTER); - } - - private void scheduleNextRegistration( - ExpiresHeader expiresHeader) { - int expires = EXPIRY_TIME; - if (expiresHeader != null) expires = expiresHeader.getExpires(); - Log.v(TAG, "Refresh registration " + expires + "s later."); - new Timer().schedule(new TimerTask() { - public void run() { - try { - process(REGISTER); - } catch (SipException e) { - Log.e(TAG, "", e); - } - }}, expires * 1000L); - } - - private String generateTag() { - // TODO: based on myself's profile - return String.valueOf((long) (Math.random() * 1000000L)); - } - - private String log(EventObject evt) { - if (evt instanceof RequestEvent) { - return ((RequestEvent) evt).getRequest().toString(); - } else if (evt instanceof ResponseEvent) { - return ((ResponseEvent) evt).getResponse().toString(); - } else { - return evt.toString(); - } - } - - public String toString() { - try { - String s = super.toString(); - return s.substring(s.indexOf("@")) + ":" + mState; - } catch (Throwable e) { - return super.toString(); - } - } - - synchronized void process(EventObject evt) throws SipException { - Log.v(TAG, " ~~~~~ " + this + ": " + mState + ": processing " - + log(evt)); - boolean processed; - - switch (mState) { - case REGISTERING: - case DEREGISTERING: - processed = registeringToReady(evt); - break; - case READY_FOR_CALL: - processed = readyForCall(evt); - break; - case INCOMING_CALL: - processed = incomingCall(evt); - break; - case INCOMING_CALL_ANSWERING: - processed = incomingCallToInCall(evt); - break; - case OUTGOING_CALL: - case OUTGOING_CALL_RING_BACK: - processed = outgoingCall(evt); - break; - case OUTGOING_CALL_CANCELING: - processed = outgoingCallToReady(evt); - break; - case IN_CALL: - processed = inCall(evt); - break; - case IN_CALL_ANSWERING: - processed = inCallAnsweringToInCall(evt); - break; - case IN_CALL_CHANGING: - processed = inCallChanging(evt); - break; - case IN_CALL_CHANGING_CANCELING: - processed = inCallChangingToInCall(evt); - break; - default: - processed = false; - } - if (!processed && !processExceptions(evt)) { - Log.w(TAG, "event not processed: " + evt); - } else { - Log.v(TAG, " ~~~~~ new state: " + mState); - } - } - - private boolean processExceptions(EventObject evt) throws SipException { - // process INVITE and CANCEL - if (isRequestEvent(Request.INVITE, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.BUSY_HERE); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, - Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); - return true; - } else if (evt instanceof TransactionTerminatedEvent) { - if (evt instanceof TimeoutEvent) { - processTimeout((TimeoutEvent) evt); - } else { - // TODO: any clean up? - } - return true; - } else if (evt instanceof DialogTerminatedEvent) { - processDialogTerminated((DialogTerminatedEvent) evt); - return true; - } - return false; - } - - private void processDialogTerminated(DialogTerminatedEvent event) { - if (mDialog == event.getDialog()) { - endCall(isInCall()); - } else { - Log.d(TAG, "not the current dialog; current=" + mDialog - + ", terminated=" + event.getDialog()); - } - } - - private void processTimeout(TimeoutEvent event) { - Log.d(TAG, "processing Timeout..." + event); - Transaction current = event.isServerTransaction() - ? mServerTransaction - : mClientTransaction; - Transaction target = event.isServerTransaction() - ? event.getServerTransaction() - : event.getClientTransaction(); - - if (current != target) { - Log.d(TAG, "not the current transaction; current=" + current - + ", timed out=" + target); - return; - } - switch (mState) { - case REGISTERING: - case INCOMING_CALL: - case INCOMING_CALL_ANSWERING: - case OUTGOING_CALL: - case OUTGOING_CALL_RING_BACK: - case OUTGOING_CALL_CANCELING: - case IN_CALL_CHANGING: - // rfc3261#section-14.1 - // if re-invite gets timed out, terminate the dialog - endCallOnError((mState == SipSessionState.IN_CALL_CHANGING), - new SipException("timed out")); - break; - case IN_CALL_ANSWERING: - case IN_CALL_CHANGING_CANCELING: - mState = SipSessionState.IN_CALL; - mListener.onError(this, new SipException("timed out")); - break; - default: - // do nothing - break; - } - } - - private boolean registeringToReady(EventObject evt) - throws SipException { - if (expectResponse(Request.REGISTER, evt)) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - - int statusCode = response.getStatusCode(); - switch (statusCode) { - case Response.OK: - if (mState == SipSessionState.REGISTERING) { - scheduleNextRegistration((ExpiresHeader) - ((ResponseEvent) evt).getResponse().getHeader( - ExpiresHeader.NAME)); - } - reset(); - mListener.onRegistrationDone(this); - return true; - case Response.UNAUTHORIZED: - case Response.PROXY_AUTHENTICATION_REQUIRED: - mSipHelper.handleChallenge((ResponseEvent)evt, mLocalProfile); - return true; - default: - if (statusCode >= 500) { - reset(); - mListener.onRegistrationFailed(this, - createCallbackException(response)); - return true; - } - } - } - return false; - } - - private boolean readyForCall(EventObject evt) throws SipException { - // expect MakeCallCommand, Invite - if (evt instanceof MakeCallCommand) { - MakeCallCommand cmd = (MakeCallCommand) evt; - mPeerProfile = cmd.getPeerProfile(); - SessionDescription sessionDescription = - cmd.getSessionDescription(); - mClientTransaction = mSipHelper.sendInvite(mLocalProfile, - mPeerProfile, sessionDescription, generateTag()); - mDialog = mClientTransaction.getDialog(); - mState = SipSessionState.OUTGOING_CALL; - mListener.onCalling(SipSessionImpl.this); - return true; - } else if (isRequestEvent(Request.INVITE, evt)) { - RequestEvent event = (RequestEvent) evt; - mServerTransaction = mSipHelper.sendRinging(event); - mDialog = mServerTransaction.getDialog(); - mInviteReceived = event; - mPeerProfile = createPeerProfile(event.getRequest()); - mState = SipSessionState.INCOMING_CALL; - mPeerSessionDescription = event.getRequest().getRawContent(); - mListener.onRinging(SipSessionImpl.this, mPeerProfile, - mPeerSessionDescription); - return true; - } else if (REGISTER == evt) { - mClientTransaction = mSipHelper.sendRegister(mLocalProfile, - generateTag(), EXPIRY_TIME); - mDialog = mClientTransaction.getDialog(); - mState = SipSessionState.REGISTERING; - return true; - } else if (DEREGISTER == evt) { - mClientTransaction = mSipHelper.sendRegister(mLocalProfile, - generateTag(), 0); - mState = SipSessionState.DEREGISTERING; - return true; - } - return false; - } - - private boolean incomingCall(EventObject evt) throws SipException { - // expect MakeCallCommand(answering) , END_CALL cmd , Cancel - if (evt instanceof MakeCallCommand) { - // answer call - mSipHelper.sendInviteOk(mInviteReceived, mLocalProfile, - ((MakeCallCommand) evt).getSessionDescription(), - generateTag(), mServerTransaction); - mState = SipSessionState.INCOMING_CALL_ANSWERING; - return true; - } else if (END_CALL == evt) { - mSipHelper.sendInviteBusyHere(mInviteReceived, - mServerTransaction); - endCall(false); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - RequestEvent event = (RequestEvent) evt; - mSipHelper.sendResponse(event, Response.OK); - mSipHelper.sendInviteRequestTerminated( - mInviteReceived.getRequest(), mServerTransaction); - endCall(false); - return true; - } - return false; - } - - private boolean incomingCallToInCall(EventObject evt) - throws SipException { - // expect ACK, CANCEL request - if (isRequestEvent(Request.ACK, evt)) { - establishCall(false); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - RequestEvent event = (RequestEvent) evt; - // TODO: what to do here? what happens when racing between - // OK-to-invite from callee and Cancel from caller - return true; - } - return false; - } - - private boolean outgoingCall(EventObject evt) throws SipException { - if (expectResponse(Request.INVITE, evt)) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - - int statusCode = response.getStatusCode(); - switch (statusCode) { - case Response.RINGING: - if (mState == SipSessionState.OUTGOING_CALL) { - mState = SipSessionState.OUTGOING_CALL_RING_BACK; - mListener.onRingingBack(this); - } - return true; - case Response.OK: - mSipHelper.sendInviteAck(event, mDialog); - mPeerSessionDescription = response.getRawContent(); - establishCall(false); - return true; - case Response.PROXY_AUTHENTICATION_REQUIRED: - mClientTransaction = mSipHelper.handleChallenge( - (ResponseEvent)evt, mLocalProfile); - mDialog = mClientTransaction.getDialog(); - return true; - default: - if (statusCode >= 400) { - // error: an ack is sent automatically by the stack - endCallOnError(false, - createCallbackException(response)); - return true; - } else if (statusCode >= 300) { - // TODO: handle 3xx (redirect) - } else { - return true; - } - } - return false; - } else if (END_CALL == evt) { - // RFC says that UA should not send out cancel when no - // response comes back yet. We are cheating for not checking - // response. - mSipHelper.sendCancel(mClientTransaction); - mState = SipSessionState.OUTGOING_CALL_CANCELING; - return true; - } - return false; - } - - private boolean outgoingCallToReady(EventObject evt) - throws SipException { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - int statusCode = response.getStatusCode(); - if (expectResponse(Request.CANCEL, evt)) { - if (statusCode == Response.OK) { - // do nothing; wait for REQUEST_TERMINATED - return true; - } - } else if (expectResponse(Request.INVITE, evt)) { - if (statusCode == Response.REQUEST_TERMINATED) { - endCall(false); - return true; - } - } else { - return false; - } - - if (statusCode >= 400) { - endCallOnError(true, createCallbackException(response)); - return true; - } - } - return false; - } - - private boolean inCall(EventObject evt) throws SipException { - // expect END_CALL cmd, BYE request, hold call (MakeCallCommand) - // OK retransmission is handled in SipStack - if (END_CALL == evt) { - // rfc3261#section-15.1.1 - mSipHelper.sendBye(mDialog); - endCall(true); - return true; - } else if (isRequestEvent(Request.INVITE, evt)) { - // got Re-INVITE - RequestEvent event = (RequestEvent) evt; - mSipHelper.sendReInviteOk(event, mLocalProfile); - mState = SipSessionState.IN_CALL_ANSWERING; - mPeerSessionDescription = event.getRequest().getRawContent(); - mListener.onCallChanged(this, mPeerSessionDescription); - return true; - } else if (isRequestEvent(Request.BYE, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - endCall(true); - return true; - } else if (evt instanceof MakeCallCommand) { - // to change call - mClientTransaction = mSipHelper.sendReinvite(mDialog, - ((MakeCallCommand) evt).getSessionDescription()); - mState = SipSessionState.IN_CALL_CHANGING; - return true; - } - return false; - } - - private boolean inCallChanging(EventObject evt) - throws SipException { - if (expectResponse(Request.INVITE, evt)) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - - int statusCode = response.getStatusCode(); - switch (statusCode) { - case Response.OK: - mSipHelper.sendInviteAck(event, mDialog); - establishCall(true); - return true; - case Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST: - case Response.REQUEST_TIMEOUT: - // rfc3261#section-14.1: re-invite failed; terminate - // the dialog - endCallOnError(true, createCallbackException(response)); - return true; - case Response.REQUEST_PENDING: - // TODO: - // rfc3261#section-14.1; re-schedule invite - return true; - default: - if (statusCode >= 400) { - // error: an ack is sent automatically by the stack - mState = SipSessionState.IN_CALL; - mListener.onError(this, - createCallbackException(response)); - return true; - } else if (statusCode >= 300) { - // TODO: handle 3xx (redirect) - } else { - return true; - } - } - return false; - } else if (END_CALL == evt) { - mSipHelper.sendCancel(mClientTransaction); - mState = SipSessionState.IN_CALL_CHANGING_CANCELING; - return true; - } - return false; - } - - private boolean inCallChangingToInCall(EventObject evt) - throws SipException { - if (expectResponse(Response.OK, Request.CANCEL, evt)) { - // do nothing; wait for REQUEST_TERMINATED - return true; - } else if (expectResponse(Response.OK, Request.INVITE, evt)) { - inCallChanging(evt); // abort Cancel - return true; - } else if (expectResponse(Response.REQUEST_TERMINATED, - Request.INVITE, evt)) { - establishCall(true); - return true; - } - return false; - } - - private boolean inCallAnsweringToInCall(EventObject evt) { - // expect ACK - if (isRequestEvent(Request.ACK, evt)) { - establishCall(true); - return true; - } - return false; - } - - private Exception createCallbackException(Response response) { - return new SipException(String.format("Response: %s (%d)", - response.getReasonPhrase(), response.getStatusCode())); - } - - private void establishCall(boolean inCall) { - if (inCall) { - mState = SipSessionState.IN_CALL; - mListener.onCallEstablished(this, mPeerSessionDescription); - } else { - SipSessionImpl newSession = createInCallSipSession(); - addSipSession(newSession); - reset(); - newSession.establishCall(true); - } - } - - private void endCall(boolean inCall) { - if (inCall) removeSipSession(this); - reset(); - mListener.onCallEnded(this); - } - - private void endCallOnError( - boolean terminating, Throwable throwable) { - if (terminating) removeSipSession(this); - reset(); - mListener.onError(this, throwable); - } - - private boolean isInCall() { - return (mState.compareTo(SipSessionState.IN_CALL) >= 0); - } - - private SipSessionImpl createInCallSipSession() { - SipSessionImpl newSession = new SipSessionImpl(mListener); - newSession.mPeerProfile = mPeerProfile; - newSession.mState = SipSessionState.IN_CALL; - newSession.mInviteReceived = mInviteReceived; - newSession.mDialog = mDialog; - newSession.mPeerSessionDescription = mPeerSessionDescription; - return newSession; - } - } - - /** - * @return true if the event is a request event matching the specified - * method; false otherwise - */ - private static boolean isRequestEvent(String method, EventObject event) { - try { - if (event instanceof RequestEvent) { - RequestEvent requestEvent = (RequestEvent) event; - return method.equals(requestEvent.getRequest().getMethod()); - } - } catch (Throwable e) { - } - return false; - } - - private static String getCseqMethod(Message message) { - return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod(); - } - - /** - * @return true if the event is a response event and the CSeqHeader method - * match the given arguments; false otherwise - */ - private static boolean expectResponse( - String expectedMethod, EventObject evt) { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); - } - return false; - } - - /** - * @return true if the event is a response event and the response code and - * CSeqHeader method match the given arguments; false otherwise - */ - private static boolean expectResponse( - int responseCode, String expectedMethod, EventObject evt) { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - if (response.getStatusCode() == responseCode) { - return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); - } - } - return false; - } - - private static SipProfile createPeerProfile(Request request) - throws SipException { - try { - FromHeader fromHeader = - (FromHeader) request.getHeader(FromHeader.NAME); - Address address = fromHeader.getAddress(); - SipURI uri = (SipURI) address.getURI(); - return new SipProfile.Builder(uri.getUser(), uri.getHost()) - .setPort(uri.getPort()) - .setDisplayName(address.getDisplayName()) - .build(); - } catch (InvalidArgumentException e) { - throw new SipException("createPeerProfile()", e); - } catch (ParseException e) { - throw new SipException("createPeerProfile()", e); - } - } - - private class MakeCallCommand extends EventObject { - private SessionDescription mSessionDescription; - - MakeCallCommand(SipProfile peerProfile, - SessionDescription sessionDescription) { - super(peerProfile); - mSessionDescription = sessionDescription; - } - - SipProfile getPeerProfile() { - return (SipProfile) getSource(); - } - - SessionDescription getSessionDescription() { - return mSessionDescription; - } - } -} diff --git a/src/android/net/sip/SipSessionLayer.java b/src/android/net/sip/SipSessionLayer.java deleted file mode 100644 index 83b31fe..0000000 --- a/src/android/net/sip/SipSessionLayer.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2009 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.util.Log; - -import java.io.IOException; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.util.HashMap; -import java.util.Map; - -import javax.sip.SipException; - -/** - * @hide - * Creates and manages multiple {@link SipSession}. - */ -public class SipSessionLayer { - private static final String TAG = SipSessionLayer.class.getSimpleName(); - - private String mMyIp; - private Map<String, SipSessionGroup> mGroupMap = - new HashMap<String, SipSessionGroup>(); - - public SipSessionLayer() throws SipException { - try { - mMyIp = getMyIp(); - } catch (IOException e) { - throw new SipException("SipSessionLayer constructor", e); - } - } - - public String getLocalIp() { - if (mMyIp != null) return mMyIp; - try { - return getMyIp(); - } catch (IOException e) { - Log.w(TAG, "getLocalIp(): " + e); - return "127.0.0.1"; - } - } - - private String getMyIp() throws IOException { - DatagramSocket s = new DatagramSocket(); - s.connect(InetAddress.getByName("192.168.1.1"), 80); - return s.getLocalAddress().getHostAddress(); - } - - public synchronized void close() { - for (String key : mGroupMap.keySet()) { - mGroupMap.get(key).close(); - } - mGroupMap.clear(); - } - - public synchronized SipSession createSipSession(SipProfile myself, - SipSessionListener listener) throws SipException { - String key = myself.getUri().toString(); - SipSessionGroup group = mGroupMap.get(key); - if (group == null) { - group = new SipSessionGroup(mMyIp, myself, listener); - mGroupMap.put(key, group); - } - return group.getDefaultSession(); - } -} diff --git a/src/android/net/sip/SipSessionListener.java b/src/android/net/sip/SipSessionListener.java deleted file mode 100644 index 98a5d53..0000000 --- a/src/android/net/sip/SipSessionListener.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2009 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; - -public interface SipSessionListener { - void onCalling(SipSession session); - void onRinging(SipSession session, SipProfile caller, - byte[] sessionDescription); - void onRingingBack(SipSession session); - void onCallEstablished(SipSession session, byte[] sessionDescription); - void onCallEnded(SipSession session); - void onCallBusy(SipSession session); - void onCallChanged(SipSession session, byte[] sessionDescription); - void onError(SipSession session, Throwable e); - - void onRegistrationDone(SipSession session); - void onRegistrationFailed(SipSession session, Throwable e); - void onRegistrationTimeout(SipSession session); -} diff --git a/src/android/net/sip/SipSessionState.java b/src/android/net/sip/SipSessionState.java deleted file mode 100644 index e9ab53d..0000000 --- a/src/android/net/sip/SipSessionState.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2009 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 SipSession} states. - */ -public enum SipSessionState { - READY_FOR_CALL, - REGISTERING, - DEREGISTERING, - INCOMING_CALL, - INCOMING_CALL_ANSWERING, - OUTGOING_CALL, - OUTGOING_CALL_RING_BACK, - OUTGOING_CALL_CANCELING, - - // the states below must be after the session being established - IN_CALL, - IN_CALL_CHANGING, - IN_CALL_CHANGING_CANCELING, - IN_CALL_ANSWERING, -} diff --git a/src/com/android/sip/SipAudioCall.java b/src/com/android/sip/SipAudioCall.java deleted file mode 100644 index e84f82f..0000000 --- a/src/com/android/sip/SipAudioCall.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip; - -import gov.nist.javax.sdp.fields.SDPKeywords; - -import com.android.sip.media.RtpFactory; -import com.android.sip.media.RtpSession; - -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.sip.SdpSessionDescription; -import android.net.sip.SessionDescription; -import android.net.sip.SipProfile; -import android.net.sip.SipSession; -import android.net.sip.SipSessionLayer; -import android.net.sip.SipSessionListener; -import android.net.sip.SipSessionState; -import android.os.Vibrator; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; - -import java.io.IOException; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.sdp.SdpException; -import javax.sip.SipException; - -/** - * Class that handles audio call over SIP. - */ -public class SipAudioCall { - private static final String TAG = SipAudioCall.class.getSimpleName(); - - public interface Listener { - void onReadyForCall(SipAudioCall call); - void onCalling(SipAudioCall call); - void onRinging(SipAudioCall call, SipProfile caller); - void onRingingBack(SipAudioCall call); - void onCallEstablished(SipAudioCall call); - void onCallEnded(SipAudioCall call); - void onCallBusy(SipAudioCall call); - void onCallHeld(SipAudioCall call); - void onError(SipAudioCall call, Throwable e); - } - - private Context mContext; - private SipProfile mLocalProfile; - private Listener mListener; - private SipSessionLayer mSipSessionLayer; - private SipSession mSipSession; - private SipSession mSipCallSession; - private SdpSessionDescription mOfferSd; - - private RtpSession mRtpSession; - private DatagramSocket mMediaSocket; - private boolean mHolding = false; - - private boolean mRingbackToneEnabled = true; - private boolean mRingtoneEnabled = true; - private Ringtone mRingtone; - private ToneGenerator mRingbackTone; - - private SipProfile mPendingCallRequest; - - public SipAudioCall(Context context, SipProfile localProfile, - Listener listener) throws SipException { - if (listener == null) { - throw new NullPointerException("listener can't be null"); - } - mContext = context; - mLocalProfile = localProfile; - mListener = listener; - mSipSessionLayer = new SipSessionLayer(); - mSipSession = mSipSessionLayer.createSipSession( - localProfile, createSipSessionListener()); - } - - // TODO: remove this after SipService is done - public void register() throws SipException { - if (mSipSession != null) mSipSession.register(); - } - - public void close() { - if (mSipSessionLayer != null) { - stopCall(); - mSipSessionLayer.close(); - mSipSessionLayer = null; - mSipSession = null; - } - stopRingbackTone(); - stopRinging(); - } - - public SipSessionState getState() { - return getActiveSession().getState(); - } - - private SipSession getActiveSession() { - return ((mSipCallSession == null) ? mSipSession - : mSipCallSession); - } - - private SipSessionListener createSipSessionListener() { - return new SipSessionListener() { - public void onCalling(SipSession session) { - Log.v(TAG, "calling... " + session); - mListener.onCalling(SipAudioCall.this); - } - - public void onRinging(SipSession session, SipProfile caller, - byte[] sessionDescription) { - startRinging(); - try { - SdpSessionDescription sd = mOfferSd = - new SdpSessionDescription(sessionDescription); - Log.v(TAG, "sip call ringing: " + session + ": " + sd); - } catch (SdpException e) { - Log.e(TAG, "create SDP", e); - } - mListener.onRinging(SipAudioCall.this, caller); - } - - public void onRingingBack(SipSession session) { - Log.v(TAG, "sip call ringing back: " + session); - startRingbackTone(); - mListener.onRingingBack(SipAudioCall.this); - } - - public void onCallEstablished( - SipSession session, byte[] sessionDescription) { - stopRingbackTone(); - stopRinging(); - try { - SdpSessionDescription sd = - new SdpSessionDescription(sessionDescription); - Log.v(TAG, "sip call established: " + session + ": " + sd); - mSipCallSession = session; - startCall(sd); - } catch (SdpException e) { - Log.e(TAG, "createSessionDescription()", e); - } - mListener.onCallEstablished(SipAudioCall.this); - } - - public void onCallEnded(SipSession session) { - Log.v(TAG, "sip call ended: " + session); - stopCall(); - stopRingbackTone(); - stopRinging(); - mSipCallSession = null; - mHolding = false; - mListener.onCallEnded(SipAudioCall.this); - } - - public void onCallBusy(SipSession session) { - Log.v(TAG, "sip call busy: " + session); - mListener.onCallBusy(SipAudioCall.this); - } - - public void onCallChanged( - SipSession session, byte[] sessionDescription) { - String message = new String(sessionDescription); - Log.v(TAG, "sip call " + message + ": " + session); - mHolding = !mHolding; - if (mHolding) { - mListener.onCallHeld(SipAudioCall.this); - } else { - mListener.onCallEstablished(SipAudioCall.this); - } - } - - public void onError(SipSession session, Throwable e) { - Log.v(TAG, "sip session error: " + e); - stopRingbackTone(); - stopRinging(); - mHolding = false; - mListener.onError(SipAudioCall.this, e); - } - - public void onRegistrationDone(SipSession session) { - Log.v(TAG, "sip registration done: " + session); - synchronized (session) { - if (mPendingCallRequest != null) { - SipProfile peerProfile = mPendingCallRequest; - mPendingCallRequest = null; - try { - makeCall(peerProfile); - } catch (SipException e) { - mListener.onError(SipAudioCall.this, e); - return; - } - } else { - mListener.onReadyForCall(SipAudioCall.this); - } - } - } - - public void onRegistrationFailed(SipSession session, Throwable e) { - Log.v(TAG, "sip registration failed: " + session + ": " + e); - if (mPendingCallRequest != null) mPendingCallRequest = null; - mListener.onError(SipAudioCall.this, e); - } - - public void onRegistrationTimeout(SipSession session) { - Log.v(TAG, "sip registration timed out: " + session); - if (mPendingCallRequest != null) mPendingCallRequest = null; - mListener.onError(SipAudioCall.this, - new SipException("SIP registration timed out")); - } - }; - } - - public void makeCall(SipProfile peerProfile) throws SipException { - synchronized (mSipSession) { - if (mSipSession.getState() == SipSessionState.READY_FOR_CALL) { - Log.v(TAG, "making call..."); - mSipSession.makeCall( - peerProfile, createOfferSessionDescription()); - } else { - Log.v(TAG, "hold the call request..."); - mPendingCallRequest = peerProfile; - } - } - } - - public void endCall() throws SipException { - stopRinging(); - getActiveSession().endCall(); - } - - public void holdCall() throws SipException { - if (mHolding) return; - getActiveSession().changeCall(createHoldSessionDescription()); - mHolding = true; - } - - public void answerCall() throws SipException { - stopRinging(); - getActiveSession().answerCall(createAnswerSessionDescription()); - } - - public void continueCall() throws SipException { - if (!mHolding) return; - getActiveSession().changeCall(createContinueSessionDescription()); - mHolding = false; - } - - private SessionDescription createOfferSessionDescription() { - RtpSession[] rtpSessions = RtpFactory.getSystemSupportedAudioSessions(); - return createSdpBuilder(rtpSessions).build(); - } - - private SessionDescription createAnswerSessionDescription() { - // choose an acceptable media from mOfferSd to answer - RtpSession rtpSession = - RtpFactory.createAudioSession(getCodecId(mOfferSd)); - return createSdpBuilder(rtpSession).build(); - } - - private SessionDescription createHoldSessionDescription() { - try { - return createSdpBuilder(mRtpSession) - .addMediaAttribute("sendonly", (String) null) - .build(); - } catch (SdpException e) { - throw new RuntimeException(e); - } - } - - private SessionDescription createContinueSessionDescription() { - return createSdpBuilder(mRtpSession).build(); - } - - private String getMediaDescription(RtpSession session) { - return String.format("%d %s/%d", session.getCodecId(), - session.getName(), session.getSampleRate()); - } - - private SdpSessionDescription.Builder createSdpBuilder(RtpSession... rtpSessions) { - String localIp = getLocalIp(); - SdpSessionDescription.Builder sdpBuilder; - try { - long sessionId = (long) (Math.random() * 10000000L); - long sessionVersion = (long) (Math.random() * 10000000L); - sdpBuilder = new SdpSessionDescription.Builder("SIP Call") - .setOrigin(mLocalProfile, sessionId, sessionVersion, - SDPKeywords.IN, SDPKeywords.IPV4, localIp) - .setConnectionInfo(SDPKeywords.IN, SDPKeywords.IPV4, - localIp); - List<Integer> codecIds = new ArrayList<Integer>(); - for (RtpSession s : rtpSessions) { - codecIds.add(s.getCodecId()); - } - sdpBuilder.addMedia("audio", getLocalMediaPort(), 1, "RTP/AVP", - codecIds.toArray(new Integer[codecIds.size()])); - for (RtpSession s : rtpSessions) { - sdpBuilder.addMediaAttribute("rtpmap", getMediaDescription(s)); - } - sdpBuilder.addMediaAttribute("rtpmap", "101 telephone-event/8000"); - // FIXME: deal with vbr codec - sdpBuilder.addMediaAttribute("ptime", "20"); - } catch (SdpException e) { - throw new RuntimeException(e); - } - return sdpBuilder; - } - - public void setInCallMode() { - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setMode(AudioManager.MODE_IN_CALL); - } - - public void setSpeakerMode() { - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setMode(AudioManager.MODE_NORMAL); - } - - private int getCodecId(SdpSessionDescription sd) { - Set<Integer> acceptableFormats = new HashSet<Integer>(); - for (RtpSession session : - RtpFactory.getSystemSupportedAudioSessions()) { - acceptableFormats.add(session.getCodecId()); - } - for (int id : sd.getMediaFormats()) { - if (acceptableFormats.contains(id)) return id; - } - Log.w(TAG, "no common codec is found, use 0"); - return 0; - } - - private void startCall(SdpSessionDescription sd) { - String peerMediaAddress = sd.getPeerMediaAddress(); - // TODO: handle multiple media fields - int peerMediaPort = sd.getPeerMediaPort(); - Log.i(TAG, "start audiocall " + peerMediaAddress + ":" + peerMediaPort); - - int localPort = getLocalMediaPort(); - int sampleRate = 8000; - int frameSize = sampleRate / 50; // 160 - try { - // TODO: get sample rate from sdp - mMediaSocket.connect(InetAddress.getByName(peerMediaAddress), - peerMediaPort); - mRtpSession = RtpFactory.createAudioSession(getCodecId(sd)); - mRtpSession.start(sampleRate, mMediaSocket); - setInCallMode(); - } catch (Exception e) { - Log.e(TAG, "call()", e); - } - Log.v(TAG, " ~~~~~~~~~~~ start media: localPort=" + localPort - + ", peer=" + peerMediaAddress + ":" + peerMediaPort); - } - - public void stopCall() { - Log.i(TAG, "stop audiocall"); - if (mRtpSession != null) { - mRtpSession.stop(); - if (mMediaSocket != null) mMediaSocket.close(); - mMediaSocket = null; - } - setSpeakerMode(); - } - - public void sendDtmf() { - SipSession activeSession = getActiveSession(); - if (SipSessionState.IN_CALL == activeSession.getState()) { - mRtpSession.sendDtmf(); - } - } - - private int getLocalMediaPort() { - if (mMediaSocket != null) return mMediaSocket.getLocalPort(); - try { - DatagramSocket s = mMediaSocket = new DatagramSocket(); - int localPort = s.getLocalPort(); - return localPort; - } catch (IOException e) { - Log.w(TAG, "getLocalMediaPort(): " + e); - throw new RuntimeException(e); - } - } - - public String getLocalIp() { - return mSipSessionLayer.getLocalIp(); - } - - public void setRingbackToneEnabled(boolean enabled) { - mRingbackToneEnabled = enabled; - } - - public void setRingtoneEnabled(boolean enabled) { - mRingtoneEnabled = enabled; - } - - private synchronized 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_MUSIC, toneVolume); - } - setInCallMode(); - mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L); - } - - private synchronized void stopRingbackTone() { - if (mRingbackTone != null) { - mRingbackTone.stopTone(); - setSpeakerMode(); - mRingbackTone.release(); - mRingbackTone = null; - } - } - - private synchronized 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 synchronized void stopRinging() { - ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE)) - .cancel(); - if (mRingtone != null) mRingtone.stop(); - } -} diff --git a/src/com/android/sip/SipMain.java b/src/com/android/sip/SipMain.java deleted file mode 100644 index 851620d..0000000 --- a/src/com/android/sip/SipMain.java +++ /dev/null @@ -1,557 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.net.sip.SipProfile; -import android.net.sip.SipSessionState; -import android.os.Bundle; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.PreferenceActivity; -import android.preference.PreferenceScreen; -import android.preference.Preference.OnPreferenceClickListener; -import android.text.TextUtils; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; - -import java.io.IOException; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.text.ParseException; -import javax.sip.SipException; - -/** - */ -public class SipMain extends PreferenceActivity - implements Preference.OnPreferenceChangeListener { - private static final String TAG = SipMain.class.getSimpleName(); - private static final int MENU_REGISTER = Menu.FIRST; - private static final int MENU_CALL = Menu.FIRST + 1; - private static final int MENU_HANGUP = Menu.FIRST + 2; - private static final int MENU_SEND_DTMF_1 = Menu.FIRST + 3; - private static final int MENU_SPEAKER_MODE = Menu.FIRST + 4; - - private Preference mCallStatus; - private EditTextPreference mPeerUri; - private EditTextPreference mServerUri; - private EditTextPreference mPassword; - private EditTextPreference mDisplayName; - private EditTextPreference mOutboundProxy; - private Preference mMyIp; - - private SipProfile mLocalProfile; - private SipAudioCall mAudioCall; - - private MyDialog mDialog; - private boolean mHolding; - private Throwable mError; - private boolean mChanged; - private boolean mSpeakerMode; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.dev_pref); - - mCallStatus = getPreferenceScreen().findPreference("call_status"); - mPeerUri = setupEditTextPreference("peer"); - mServerUri = setupEditTextPreference("server_address"); - mPassword = (EditTextPreference) - getPreferenceScreen().findPreference("password"); - mPassword.setOnPreferenceChangeListener(this); - mDisplayName = setupEditTextPreference("display_name"); - mOutboundProxy = setupEditTextPreference("proxy_address"); - mMyIp = getPreferenceScreen().findPreference("my_ip"); - mMyIp.setOnPreferenceClickListener( - new OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - // for testing convenience: copy my IP to server address - if (TextUtils.isEmpty(mServerUri.getText())) { - String myIp = mMyIp.getSummary().toString(); - String uri = "test@" + myIp + ":5060"; - mServerUri.setText(uri); - mServerUri.setSummary(uri); - } - return true; - } - }); - - mCallStatus.setOnPreferenceClickListener( - new OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - actOnCallStatus(); - return true; - } - }); - setCallStatus(); - - new Thread(new Runnable() { - public void run() { - final String localIp = getLocalIp(); - runOnUiThread(new Runnable() { - public void run() { - mMyIp.setSummary(localIp); - } - }); - } - }).start(); - } - - private void createSipAudioCall() throws SipException { - if ((mAudioCall == null) || mChanged) { - if (mAudioCall != null) mAudioCall.close(); - mAudioCall = new SipAudioCall(this, createLocalSipProfile(), - createListener()); - mChanged = false; - Log.v(TAG, "info changed; recreate AudioCall isntance"); - } - } - - private EditTextPreference setupEditTextPreference(String key) { - EditTextPreference pref = (EditTextPreference) - getPreferenceScreen().findPreference(key); - pref.setOnPreferenceChangeListener(this); - pref.setSummary(pref.getText()); - return pref; - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mAudioCall != null) mAudioCall.close(); - } - - public boolean onPreferenceChange(Preference pref, Object newValue) { - String value = (String) newValue; - if (value == null) value = ""; - if (pref != mPassword) pref.setSummary(value); - if ((pref != mPeerUri) - && !value.equals(((EditTextPreference) pref).getText())) { - mChanged = true; - } - return true; - } - - private String getText(EditTextPreference preference) { - CharSequence text = preference.getText(); - return ((text == null) ? "" : text.toString()); - } - - private SipProfile createLocalSipProfile() throws SipException { - try { - if ((mLocalProfile == null) || mChanged) { - String serverUri = getText(mServerUri); - if (TextUtils.isEmpty(serverUri)) { - throw new SipException("Server address missing"); - } - mLocalProfile = new SipProfile.Builder(serverUri) - .setPassword(getText(mPassword)) - .setDisplayName(getText(mDisplayName)) - .setOutboundProxy(getText(mOutboundProxy)) - .build(); - } - return mLocalProfile; - } catch (ParseException e) { - throw new SipException("createLoalSipProfile", e); - } - } - - private SipProfile createPeerSipProfile() { - try { - return new SipProfile.Builder(getPeerUri()).build(); - } catch (ParseException e) { - throw new RuntimeException(e); - } - } - - private void setCallStatus(Throwable e) { - mError = e; - setCallStatus(); - } - - private void setCallStatus() { - runOnUiThread(new Runnable() { - public void run() { - mCallStatus.setSummary(getCallStatus()); - mError = null; - } - }); - } - - private void showCallNotificationDialog(SipProfile caller) { - mDialog = new CallNotificationDialog(caller); - runOnUiThread(new Runnable() { - public void run() { - showDialog(mDialog.getId()); - } - }); - } - - private SipAudioCall.Listener createListener() { - return new SipAudioCall.Listener() { - public void onCalling(SipAudioCall call) { - setCallStatus(); - } - - public void onReadyForCall(SipAudioCall call) { - setCallStatus(); - } - - public void onRinging(SipAudioCall call, SipProfile caller) { - showCallNotificationDialog(caller); - setCallStatus(); - } - - public void onRingingBack(SipAudioCall call) { - setCallStatus(); - } - - public void onCallEstablished(SipAudioCall call) { - setAllPreferencesEnabled(false); - setCallStatus(); - } - - public void onCallEnded(SipAudioCall call) { - setCallStatus(); - setAllPreferencesEnabled(true); - } - - public void onCallBusy(SipAudioCall call) { - setCallStatus(); - } - - public void onCallHeld(SipAudioCall call) { - setCallStatus(); - } - - public void onError(SipAudioCall call, Throwable e) { - mError = e; - setCallStatus(); - } - }; - } - - private void register() { - try { - createSipAudioCall(); - mAudioCall.register(); - } catch (SipException e) { - Log.e(TAG, "makeCall()", e); - setCallStatus(e); - } - } - - private void makeCall() { - try { - createSipAudioCall(); - mAudioCall.makeCall(createPeerSipProfile()); - } catch (SipException e) { - Log.e(TAG, "makeCall()", e); - setCallStatus(e); - } - } - - private void endCall() { - try { - mAudioCall.endCall(); - mSpeakerMode = false; - } catch (SipException e) { - Log.e(TAG, "endCall()", e); - setCallStatus(e); - } - } - - private void holdOrEndCall() { - try { - if (Math.random() > 0.4) { - mAudioCall.holdCall(); - } else { - mAudioCall.endCall(); - } - } catch (SipException e) { - Log.e(TAG, "holdOrEndCall()", e); - setCallStatus(e); - } - } - - private void answerCall() { - try { - mAudioCall.answerCall(); - } catch (SipException e) { - Log.e(TAG, "answerCall()", e); - setCallStatus(e); - } - } - - private void answerOrEndCall() { - if (Math.random() > 0) { - answerCall(); - } else { - endCall(); - } - } - - private void continueCall() { - try { - mAudioCall.continueCall(); - } catch (SipException e) { - Log.e(TAG, "continueCall()", e); - setCallStatus(e); - } - } - - - private void setAllPreferencesEnabled(final boolean enabled) { - runOnUiThread(new Runnable() { - public void run() { - for (Preference preference : allPreferences()) { - preference.setEnabled(enabled); - } - } - }); - } - - private Preference[] allPreferences() { - return new Preference[] { - mCallStatus, mPeerUri, mServerUri, mPassword, mDisplayName, mOutboundProxy, mMyIp - }; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - SipSessionState state = ((mAudioCall == null) || mChanged) - ? SipSessionState.READY_FOR_CALL - : mAudioCall.getState(); - - Log.v(TAG, "actOnCallStatus(), status=" + state); - menu.clear(); - switch (state) { - case READY_FOR_CALL: - menu.add(0, MENU_REGISTER, 0, R.string.menu_register); - menu.add(0, MENU_CALL, 0, R.string.menu_call); - break; - case IN_CALL: - menu.add(0, MENU_SPEAKER_MODE, 0, (mSpeakerMode ? - R.string.menu_incall_mode : R.string.menu_speaker_mode)); - menu.add(0, MENU_SEND_DTMF_1, 0, R.string.menu_send_dtmf); - /* pass through */ - default: - menu.add(0, MENU_HANGUP, 0, R.string.menu_hangup); - } - return true; - } - - @Override - public synchronized boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case MENU_REGISTER: - register(); - setCallStatus(); - return true; - - case MENU_CALL: - makeCall(); - return true; - - case MENU_HANGUP: - endCall(); - return true; - - case MENU_SPEAKER_MODE: - mSpeakerMode = !mSpeakerMode; - if (mSpeakerMode == true) { - mAudioCall.setSpeakerMode(); - } else { - mAudioCall.setInCallMode(); - } - return true; - - case MENU_SEND_DTMF_1: - mAudioCall.sendDtmf(); - return true; - } - return super.onOptionsItemSelected(item); - } - - private String getPeerUri() { - return getText(mPeerUri); - } - - private void setValue(EditTextPreference pref, String value) { - pref.setSummary((value == null) ? "" : value.trim()); - } - - private void setSummary(Preference pref, int fieldNameId, String v) { - setSummary(pref, fieldNameId, v, true); - } - - private void setSummary(Preference pref, int fieldNameId, String v, - boolean required) { - String formatString = required - ? getString(R.string.field_not_set) - : getString(R.string.field_not_set_optional); - pref.setSummary(TextUtils.isEmpty(v) - ? String.format(formatString, getString(fieldNameId)) - : v); - } - - private String getCallStatus() { - if (mError != null) return mError.getMessage(); - if (mAudioCall == null) return "Ready to call (not registered)"; - switch (mAudioCall.getState()) { - case REGISTERING: - return "Registering..."; - case READY_FOR_CALL: - return "Ready for call"; - case INCOMING_CALL: - return "Ringing..."; - case INCOMING_CALL_ANSWERING: - return "Answering..."; - case OUTGOING_CALL: - return "Calling..."; - case OUTGOING_CALL_RING_BACK: - return "Ringing back..."; - case OUTGOING_CALL_CANCELING: - return "Cancelling..."; - case IN_CALL: - return (mHolding ? "On hold" : "Established"); - case IN_CALL_CHANGING: - return "Changing session..."; - case IN_CALL_ANSWERING: - return "Changing session answering..."; - default: - return "Unknown"; - } - } - - private void actOnCallStatus() { - if ((mAudioCall == null) || mChanged) { - register(); - } else { - switch (mAudioCall.getState()) { - case READY_FOR_CALL: - makeCall(); - break; - case INCOMING_CALL: - answerOrEndCall(); - break; - case OUTGOING_CALL_RING_BACK: - case OUTGOING_CALL: - case IN_CALL_CHANGING: - endCall(); - break; - case IN_CALL: - if (!mHolding) { - holdOrEndCall(); - } else { - continueCall(); - } - break; - case OUTGOING_CALL_CANCELING: - case REGISTERING: - case INCOMING_CALL_ANSWERING: - default: - // do nothing - break; - } - } - - setCallStatus(); - } - - @Override - protected Dialog onCreateDialog (int id) { - return ((mDialog == null) ? null : mDialog.createDialog(id)); - } - - @Override - protected void onPrepareDialog (int id, Dialog dialog) { - if (mDialog != null) mDialog.prepareDialog(id, dialog); - } - - private class CallNotificationDialog implements MyDialog { - private SipProfile mCaller; - - CallNotificationDialog(SipProfile caller) { - mCaller = caller; - } - - public int getId() { - return 0; - } - - private String getCallerName() { - String name = mCaller.getDisplayName(); - if (TextUtils.isEmpty(name)) name = mCaller.getUri().toString(); - return name; - } - - public Dialog createDialog(int id) { - if (id != getId()) return null; - Log.d(TAG, "create call notification dialog"); - return new AlertDialog.Builder(SipMain.this) - .setTitle(getCallerName()) - .setIcon(android.R.drawable.ic_dialog_alert) - .setPositiveButton("Answer", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int w) { - answerCall(); - } - }) - .setNegativeButton("Hang up", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int w) { - endCall(); - } - }) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - endCall(); - } - }) - .create(); - } - - public void prepareDialog(int id, Dialog dialog) { - if (id != getId()) return; - dialog.setTitle(getCallerName()); - } - } - - private interface MyDialog { - int getId(); - Dialog createDialog(int id); - void prepareDialog(int id, Dialog dialog); - } - - private String getLocalIp() { - try { - DatagramSocket s = new DatagramSocket(); - s.connect(InetAddress.getByName("192.168.1.1"), 80); - return s.getLocalAddress().getHostAddress(); - } catch (IOException e) { - Log.w(TAG, "getLocalIp(): " + e); - return "127.0.0.1"; - } - } -} diff --git a/src/com/android/sip/media/Decoder.java b/src/com/android/sip/media/Decoder.java deleted file mode 100644 index c0f1060..0000000 --- a/src/com/android/sip/media/Decoder.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip.media; - -import java.io.IOException; - -public interface Decoder { - int getSampleCount(int frameSize); - - /** - * Decodes from the encoded data array. - * - * @param result the decoded result array - * @param src encoded data - * @param count valid data length in src - * @param offset offset of data bytes in src - */ - int decode(short[] result, byte[] src, int count, int offset) - throws IOException; -} diff --git a/src/com/android/sip/media/Encoder.java b/src/com/android/sip/media/Encoder.java deleted file mode 100644 index f1845e5..0000000 --- a/src/com/android/sip/media/Encoder.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip.media; - -public interface Encoder { - int getSampleCount(int frameSize); - - /** - * Encodes the sample data. - * - * @param src sample data - * @param count valid data length in src - * @param result the encoded result array - * @param offset offset of data bytes in result - */ - int encode(short[] src, int count, byte[] result, int offset); -} diff --git a/src/com/android/sip/media/G711ACodec.java b/src/com/android/sip/media/G711ACodec.java deleted file mode 100644 index 52b2cb5..0000000 --- a/src/com/android/sip/media/G711ACodec.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip.media; - -/** - * G.711 codec. This class provides a-law conversion. - */ -public class G711ACodec implements Encoder, Decoder { - // s0000000wxyz...s000wxyz - // s0000001wxyz...s001wxyz - // s000001wxyza...s010wxyz - // s00001wxyzab...s011wxyz - // s0001wxyzabc...s100wxyz - // s001wxyzabcd...s101wxyz - // s01wxyzabcde...s110wxyz - // s1wxyzabcdef...s111wxyz - - private static byte[] table12to8 = new byte[4096]; - private static short[] table8to16 = new short[256]; - - static { - // b12 --> b8 - for (int m = 0; m < 16; m++) { - int v = m ^ 0x55; - table12to8[m] = (byte) v; - table12to8[4095 - m] = (byte) (v + 128); - } - for (int p = 1, q = 0x10; p <= 0x40; p <<= 1, q+=0x10) { - for (int i = 0, j = (p << 4); i < 16; i++, j += p) { - int v = (i + q) ^ 0x55; - byte value1 = (byte) v; - byte value2 = (byte) (v + 128); - for (int m = j, e = j + p; m < e; m++) { - table12to8[m] = value1; - table12to8[4095 - m] = value2; - } - } - } - - // b8 --> b16 - for (int m = 0; m < 16; m++) { - int v = m << 4; - table8to16[m ^ 0x55] = (short) v; - table8to16[(m + 128) ^ 0x55] = (short) (65536 - v); - } - for (int q = 1; q <= 7; q++) { - for (int i = 0, m = (q << 4); i < 16; i++, m++) { - int v = (i + 0x10) << (q + 3); - table8to16[m ^ 0x55] = (short) v; - table8to16[(m + 128) ^ 0x55] = (short) (65536 - v); - } - } - } - - public int decode(short[] b16, byte[] b8, int count, int offset) { - for (int i = 0, j = offset; i < count; i++, j++) { - b16[i] = table8to16[b8[j] & 0xff]; - } - return count; - } - - public int encode(short[] b16, int count, byte[] b8, int offset) { - - for (int i = 0, j = offset; i < count; i++, j++) { - b8[j] = table12to8[(b16[i] & 0xffff) >> 4]; - } - return count; - } - - public int getSampleCount(int frameSize) { - return frameSize; - } -} diff --git a/src/com/android/sip/media/G711UCodec.java b/src/com/android/sip/media/G711UCodec.java deleted file mode 100644 index b8f681a..0000000 --- a/src/com/android/sip/media/G711UCodec.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip.media; - -/** - * G.711 codec. This class provides u-law conversion. - */ -public class G711UCodec implements Encoder, Decoder { - // s00000001wxyz...s000wxyz - // s0000001wxyza...s001wxyz - // s000001wxyzab...s010wxyz - // s00001wxyzabc...s011wxyz - // s0001wxyzabcd...s100wxyz - // s001wxyzabcde...s101wxyz - // s01wxyzabcdef...s110wxyz - // s1wxyzabcdefg...s111wxyz - - private static byte[] table13to8 = new byte[8192]; - private static short[] table8to16 = new short[256]; - - static { - // b13 --> b8 - for (int p = 1, q = 0; p <= 0x80; p <<= 1, q+=0x10) { - for (int i = 0, j = (p << 4) - 0x10; i < 16; i++, j += p) { - int v = (i + q) ^ 0x7F; - byte value1 = (byte) v; - byte value2 = (byte) (v + 128); - for (int m = j, e = j + p; m < e; m++) { - table13to8[m] = value1; - table13to8[8191 - m] = value2; - } - } - } - - // b8 --> b16 - for (int q = 0; q <= 7; q++) { - for (int i = 0, m = (q << 4); i < 16; i++, m++) { - int v = (((i + 0x10) << q) - 0x10) << 3; - table8to16[m ^ 0x7F] = (short) v; - table8to16[(m ^ 0x7F) + 128] = (short) (65536 - v); - } - } - } - - public int decode(short[] b16, byte[] b8, int count, int offset) { - for (int i = 0, j = offset; i < count; i++, j++) { - b16[i] = table8to16[b8[j] & 0xFF]; - } - return count; - } - - public int encode(short[] b16, int count, byte[] b8, int offset) { - - for (int i = 0, j = offset; i < count; i++, j++) { - b8[j] = table13to8[(b16[i] >> 4) & 0x1FFF]; - } - return count; - } - - public int getSampleCount(int frameSize) { - return frameSize; - } -} diff --git a/src/com/android/sip/media/RtpAudioSession.java b/src/com/android/sip/media/RtpAudioSession.java deleted file mode 100644 index 0c7f616..0000000 --- a/src/com/android/sip/media/RtpAudioSession.java +++ /dev/null @@ -1,656 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip.media; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.UnknownHostException; - -import android.media.AudioFormat; -import android.media.AudioManager; -import android.media.AudioRecord; -import android.media.AudioTrack; -import android.media.MediaRecorder; -import android.os.Process; -import android.util.Log; - -class RtpAudioSession implements RtpSession { - private static final String TAG = RtpAudioSession.class.getSimpleName(); - private static final int AUDIO_SAMPLE_RATE = 8000; - private static final int MAX_ALLOWABLE_LATENCY = 500; // ms - - private boolean mRunning = false; - private RecordTask mRecordTask; - private PlayTask mPlayTask; - - private DatagramSocket mSocket; - private InetAddress mRemoteAddr; - private int mRemotePort; - private boolean mSendDtmf = false; - private int mCodecId; - - private NoiseGenerator mNoiseGenerator = new NoiseGenerator(); - - RtpAudioSession(int codecId) { - mCodecId = codecId; - } - - private void init(int remoteSampleRate, DatagramSocket socket) { - mRemoteAddr = socket.getInetAddress(); - mRemotePort = socket.getPort(); - mSocket = socket; - int localFrameSize = AUDIO_SAMPLE_RATE / 50; // 50 frames / sec - mRecordTask = new RecordTask(AUDIO_SAMPLE_RATE, localFrameSize); - mPlayTask = new PlayTask(remoteSampleRate, localFrameSize); - Log.v(TAG, "create RtpSession: to connect to " + mRemoteAddr + ":" - + mRemotePort + " using codec " + mCodecId); - } - - public int getCodecId() { - return mCodecId; - } - - public int getSampleRate() { - return AUDIO_SAMPLE_RATE; - } - - public String getName() { - switch (mCodecId) { - case 8: return "PCMA"; - default: return "PCMU"; - } - } - - public void start(int remoteSampleRate, DatagramSocket connectedSocket) - throws IOException { - init(remoteSampleRate, connectedSocket); - if (mRunning) return; - - mRunning = true; - Log.v(TAG, "start RtpSession: connect to " + mRemoteAddr + ":" - + mRemotePort); - if (mRemoteAddr != null) { - mRecordTask.start(mRemoteAddr, mRemotePort); - } - mPlayTask.start(); - } - - public synchronized void stop() { - mRunning = false; - Log.v(TAG, "stop RtpSession: measured volume = " - + mNoiseGenerator.mMeasuredVolume); - // wait until player is stopped - for (int i = 20; (i > 0) && !mPlayTask.isStopped(); i--) { - Log.v(TAG, " wait for player to stop..."); - try { - wait(20); - } catch (InterruptedException e) { - // ignored - } - } - } - - public void sendDtmf() { - mSendDtmf = true; - } - - private void startRecordTask(InetAddress remoteAddr, int remotePort) { - mRemoteAddr = remoteAddr; - mRemotePort = remotePort; - if ((mRemoteAddr != null) && mRunning && !mSocket.isConnected()) { - mRecordTask.start(remoteAddr, remotePort); - } - } - - private class PlayTask implements Runnable { - private int mSampleRate; - private int mFrameSize; - private AudioPlayer mPlayer; - - PlayTask(int sampleRate, int frameSize) { - mSampleRate = sampleRate; - mFrameSize = frameSize; - } - - void start() { - new Thread(this).start(); - } - - boolean isStopped() { - return (mPlayer == null) - || (mPlayer.getPlayState() == AudioTrack.PLAYSTATE_STOPPED); - } - - public void run() { - Decoder decoder = RtpFactory.createDecoder(mCodecId); - int playBufferSize = decoder.getSampleCount(mFrameSize); - short[] playBuffer = new short[playBufferSize]; - - RtpReceiver receiver = new RtpReceiver(mFrameSize); - byte[] buffer = receiver.getBuffer(); - int offset = receiver.getPayloadOffset(); - - int minBufferSize = AudioTrack.getMinBufferSize(mSampleRate, - AudioFormat.CHANNEL_CONFIGURATION_MONO, - AudioFormat.ENCODING_PCM_16BIT) * 6; - minBufferSize = Math.max(minBufferSize, playBufferSize); - int bufferHighMark = minBufferSize / 2 * 8 / 10; - Log.d(TAG, " play buffer = " + minBufferSize + ", high water mark=" - + bufferHighMark); - AudioTrack aplayer = new AudioTrack(AudioManager.STREAM_MUSIC, - mSampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, - AudioFormat.ENCODING_PCM_16BIT, minBufferSize, - AudioTrack.MODE_STREAM); - AudioPlayer player = mPlayer = - new AudioPlayer(aplayer, minBufferSize, mFrameSize); - - Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); - player.play(); - int playState = player.getPlayState(); - boolean socketConnected = mSocket.isConnected(); - long receiveCount = 0; - long cyclePeriod = mFrameSize * 1000L / mSampleRate; - long cycleStart = 0; - int seqNo = 0; - int packetLossCount = 0; - int bytesDropped = 0; - - player.flush(); - int writeHead = player.getPlaybackHeadPosition(); - - // start measurement after first packet arrival - try { - receiver.receive(); - Log.d(TAG, "received first packet"); - } catch (IOException e) { - Log.e(TAG, "receive error; stop player", e); - player.stop(); - player.release(); - return; - } - cycleStart = System.currentTimeMillis(); - seqNo = receiver.getSequenceNumber(); - - if (!socketConnected) { - socketConnected = true; - startRecordTask(receiver.getRemoteAddress(), - receiver.getRemotePort()); - } - - long startTime = System.currentTimeMillis(); - long virtualClock = startTime; - float delta = 0f; - - while (mRunning) { - try { - int count = receiver.receive(); - int decodeCount = decoder.decode( - playBuffer, buffer, count, offset); - mNoiseGenerator.measureVolume(playBuffer, 0, decodeCount); - if (playState == AudioTrack.PLAYSTATE_STOPPED) { - player.play(); - playState = player.getPlayState(); - } - - receiveCount ++; - int sn = receiver.getSequenceNumber(); - int lossCount = sn - seqNo - 1; - if (lossCount > 0) { - packetLossCount += lossCount; - virtualClock += lossCount * cyclePeriod; - } - virtualClock += cyclePeriod; - long now = System.currentTimeMillis(); - long late = now - virtualClock; - if (late < 0) virtualClock = now; - - delta = delta * 0.96f + late * 0.04f; - if (delta > MAX_ALLOWABLE_LATENCY) { - delta = MAX_ALLOWABLE_LATENCY; - } - late -= (long) delta; - - if (late > 100) { - // drop - bytesDropped += decodeCount; - if (LogRateLimiter.allowLogging(now)) { - Log.d(TAG, " drop " + sn + ":" + decodeCount - + ", late: " + late + ", d=" + delta); - } - cycleStart = now; - seqNo = sn; - continue; - } - int buffered = writeHead - player.getPlaybackHeadPosition(); - if (buffered > bufferHighMark) { - player.flush(); - buffered = 0; - writeHead = player.getPlaybackHeadPosition(); - if (LogRateLimiter.allowLogging(now)) { - Log.d(TAG, " ~~~ flush: set writeHead to " - + writeHead); - } - } - - writeHead += player.write(playBuffer, 0, decodeCount); - - cycleStart = now; - seqNo = sn; - } catch (IOException e) { - Log.w(TAG, "network disconnected; playback stopped", e); - player.stop(); - playState = player.getPlayState(); - } - } - Log.d(TAG, " receiveCount = " + receiveCount); - Log.d(TAG, " # packets lost =" + packetLossCount); - Log.d(TAG, " bytes dropped =" + bytesDropped); - Log.d(TAG, "stop sound playing..."); - player.stop(); - player.flush(); - player.release(); - } - } - - private class RecordTask implements Runnable { - private int mSampleRate; - private int mFrameSize; - - RecordTask(int sampleRate, int frameSize) { - mSampleRate = sampleRate; - mFrameSize = frameSize; - } - - void start(InetAddress addr, int port) { - Log.d(TAG, "start RecordTask, connect to " + addr + ":" + port); - mSocket.connect(addr, port); - new Thread(this).start(); - } - - private void adjustMicGain(short[] buf, int len, int factor) { - int i,j; - for (i = 0; i < len; i++) { - j = buf[i]; - if (j > 32768/factor) { - buf[i] = 32767; - } else if (j < -(32768/factor)) { - buf[i] = -32767; - } else { - buf[i] = (short)(factor*j); - } - } - } - - public void run() { - Encoder encoder = RtpFactory.createEncoder(mCodecId); - int recordBufferSize = encoder.getSampleCount(mFrameSize); - short[] recordBuffer = new short[recordBufferSize]; - RtpSender sender = new RtpSender(mFrameSize); - byte[] buffer = sender.getBuffer(); - int offset = sender.getPayloadOffset(); - - int bufferSize = AudioRecord.getMinBufferSize(mSampleRate, - AudioFormat.CHANNEL_CONFIGURATION_MONO, - AudioFormat.ENCODING_PCM_16BIT) * 3 / 2; - - AudioRecord recorder = new AudioRecord( - MediaRecorder.AudioSource.MIC, mSampleRate, - AudioFormat.CHANNEL_CONFIGURATION_MONO, - AudioFormat.ENCODING_PCM_16BIT, bufferSize); - - recorder.startRecording(); - Log.d(TAG, "start sound recording..." + recorder.getState()); - - // skip the first read, kick off read pipeline - recorder.read(recordBuffer, 0, recordBufferSize); - - long sendCount = 0; - long startTime = System.currentTimeMillis(); - - while (mRunning) { - int count = recorder.read(recordBuffer, 0, recordBufferSize); - - // TODO: remove the mic gain if the issue is fixed on Passion. - adjustMicGain(recordBuffer, count, 16); - - int encodeCount = - encoder.encode(recordBuffer, count, buffer, offset); - try { - sender.send(encodeCount); - if (mSendDtmf) { - recorder.stop(); - sender.sendDtmf(); - mSendDtmf = false; - recorder.startRecording(); - } - } catch (IOException e) { - if (mRunning) Log.e(TAG, "send error, stop sending", e); - break; - } - - sendCount ++; - } - long now = System.currentTimeMillis(); - Log.d(TAG, " sendCount = " + sendCount); - Log.d(TAG, " avg send cycle =" - + ((double) (now - startTime) / sendCount)); - Log.d(TAG, "stop sound recording..."); - recorder.stop(); - } - } - - private class RtpReceiver { - RtpPacket mPacket; - DatagramPacket mDatagram; - - RtpReceiver(int size) { - byte[] buffer = new byte[size + 12]; - mPacket = new RtpPacket(buffer); - mPacket.setPayloadType(mCodecId); - mDatagram = new DatagramPacket(buffer, buffer.length); - } - - byte[] getBuffer() { - return mPacket.getRawPacket(); - } - - int getPayloadOffset() { - return 12; - } - - // return received payload size - int receive() throws IOException { - DatagramPacket datagram = mDatagram; - mSocket.receive(datagram); - return datagram.getLength() - 12; - } - - InetAddress getRemoteAddress() { - return mDatagram.getAddress(); - } - - int getRemotePort() { - return mDatagram.getPort(); - } - - int getSequenceNumber() { - return mPacket.getSequenceNumber(); - } - } - - private class RtpSender extends RtpReceiver { - private int mSequence = 0; - private long mTimeStamp = 0; - - RtpSender(int size) { - super(size); - } - - void sendDtmf() throws IOException { - byte[] buffer = getBuffer(); - - RtpPacket packet = mPacket; - packet.setPayloadType(101); - packet.setPayloadLength(4); - - mTimeStamp += 160; - packet.setTimestamp(mTimeStamp); - DatagramPacket datagram = mDatagram; - datagram.setLength(packet.getPacketLength()); - int duration = 480; - buffer[12] = 1; - buffer[13] = 0; - buffer[14] = (byte)(duration >> 8); - buffer[15] = (byte)duration; - for (int i = 0; i < 3; i++) { - packet.setSequenceNumber(mSequence++); - mSocket.send(datagram); - try { - Thread.sleep(20); - } catch (Exception e) { - } - } - mTimeStamp += 480; - packet.setTimestamp(mTimeStamp); - buffer[12] = 1; - buffer[13] = (byte)0x80; - buffer[14] = (byte)(duration >> 8); - buffer[15] = (byte)duration; - for (int i = 0; i < 3; i++) { - packet.setSequenceNumber(mSequence++); - mSocket.send(datagram); - } - } - - void send(int count) throws IOException { - mTimeStamp += count; - RtpPacket packet = mPacket; - packet.setSequenceNumber(mSequence++); - packet.setPayloadType(mCodecId); - packet.setTimestamp(mTimeStamp); - packet.setPayloadLength(count); - - DatagramPacket datagram = mDatagram; - datagram.setLength(packet.getPacketLength()); - mSocket.send(datagram); - } - } - - private class NoiseGenerator { - private static final int AMP = 1000; - private static final int TURN_DOWN_RATE = 80; - private static final int NOISE_LENGTH = 160; - - private short[] mNoiseBuffer = new short[NOISE_LENGTH]; - private int mMeasuredVolume = 0; - - short[] makeNoise() { - final int len = NOISE_LENGTH; - short volume = (short) (mMeasuredVolume / TURN_DOWN_RATE / AMP); - double volume2 = volume * 2.0; - int m = 8; - for (int i = 0; i < len; i+=m) { - short v = (short) (Math.random() * volume2); - v -= volume; - for (int j = 0, k = i; (j < m) && (k < len); j++, k++) { - mNoiseBuffer[k] = v; - } - } - return mNoiseBuffer; - } - - void measureVolume(short[] audioData, int offset, int count) { - for (int i = 0, j = offset; i < count; i++, j++) { - mMeasuredVolume = (mMeasuredVolume * 9 - + Math.abs((int) audioData[j]) * AMP) / 10; - } - } - - int getNoiseLength() { - return mNoiseBuffer.length; - } - } - - // Use another thread to play back to avoid playback blocks network - // receiving thread - private class AudioPlayer implements Runnable, - AudioTrack.OnPlaybackPositionUpdateListener { - private short[] mBuffer; - private int mStartMarker; - private int mEndMarker; - private AudioTrack mTrack; - private int mFrameSize; - private int mOffset; - private boolean mIsPlaying = false; - private boolean mNotificationStarted = false; - - AudioPlayer(AudioTrack track, int bufferSize, int frameSize) { - mTrack = track; - mBuffer = new short[bufferSize]; - mFrameSize = frameSize; - } - - synchronized int write(short[] buffer, int offset, int count) { - int bufferSize = mBuffer.length; - while (getBufferedDataSize() + count > bufferSize) { - try { - wait(); - } catch (Exception e) { - // - } - } - - int end = mEndMarker % bufferSize; - if (end + count > bufferSize) { - int partialSize = bufferSize - end; - System.arraycopy(buffer, offset, mBuffer, end, partialSize); - System.arraycopy(buffer, offset + partialSize, mBuffer, 0, - count - partialSize); - } else { - System.arraycopy(buffer, 0, mBuffer, end, count); - } - mEndMarker += count; - - return count; - } - - synchronized void flush() { - mEndMarker = mStartMarker; - notify(); - } - - int getBufferedDataSize() { - return mEndMarker - mStartMarker; - } - - synchronized void play() { - if (!mIsPlaying) { - mTrack.setPositionNotificationPeriod(mFrameSize); - mTrack.setPlaybackPositionUpdateListener(this); - mIsPlaying = true; - mTrack.play(); - mOffset = mTrack.getPlaybackHeadPosition(); - - // start initial noise feed, to kick off periodic notification - new Thread(this).start(); - } - } - - synchronized void stop() { - mIsPlaying = false; - mTrack.stop(); - mTrack.flush(); - mTrack.setPlaybackPositionUpdateListener(null); - } - - synchronized void release() { - mTrack.release(); - } - - public synchronized void run() { - Log.d(TAG, "start initial noise feed"); - int count = 0; - long waitTime = mNoiseGenerator.getNoiseLength() / 8; // ms - while (!mNotificationStarted && mIsPlaying) { - feedNoise(); - count++; - try { - this.wait(waitTime); - } catch (InterruptedException e) { - Log.e(TAG, "initial noise feed error: " + e); - break; - } - } - Log.d(TAG, "stop initial noise feed: " + count); - } - - int getPlaybackHeadPosition() { - return mStartMarker; - } - - int getPlayState() { - return mTrack.getPlayState(); - } - - int getState() { - return mTrack.getState(); - } - - // callback - public void onMarkerReached(AudioTrack track) { - } - - // callback - public synchronized void onPeriodicNotification(AudioTrack track) { - if (!mNotificationStarted) { - mNotificationStarted = true; - Log.d(TAG, " ~~~ notification callback started"); - } else if (!mIsPlaying) { - Log.d(TAG, " ~x~ notification callback quit"); - return; - } - try { - writeToTrack(); - } catch (IllegalStateException e) { - Log.e(TAG, "writeToTrack()", e); - } - } - - private void feedNoise() { - short[] noiseBuffer = mNoiseGenerator.makeNoise(); - mOffset += mTrack.write(noiseBuffer, 0, noiseBuffer.length); - } - - private synchronized void writeToTrack() { - if (mStartMarker == mEndMarker) { - int head = mTrack.getPlaybackHeadPosition() - mOffset; - if ((mStartMarker - head) <= 320) feedNoise(); - return; - } - - int count = mFrameSize; - if (count < getBufferedDataSize()) count = getBufferedDataSize(); - - int bufferSize = mBuffer.length; - int start = mStartMarker % bufferSize; - if ((start + count) <= bufferSize) { - mStartMarker += mTrack.write(mBuffer, start, count); - } else { - int partialSize = bufferSize - start; - mStartMarker += mTrack.write(mBuffer, start, partialSize); - mStartMarker += mTrack.write(mBuffer, 0, count - partialSize); - } - notify(); - } - } - - private static class LogRateLimiter { - private static final long MIN_TIME = 1000; - private static long mLastTime; - - private static boolean allowLogging(long now) { - if ((now - mLastTime) < MIN_TIME) { - return false; - } else { - mLastTime = now; - return true; - } - } - } -} diff --git a/src/com/android/sip/media/RtpFactory.java b/src/com/android/sip/media/RtpFactory.java deleted file mode 100644 index 8dc185f..0000000 --- a/src/com/android/sip/media/RtpFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip.media; - -import android.util.Log; - -public class RtpFactory { - public static RtpSession[] getSystemSupportedAudioSessions() { - return new RtpSession[] { - new RtpAudioSession(0), new RtpAudioSession(8)}; - } - - // returns null if codecId is not supported by the system - public static RtpSession createAudioSession(int codecId) { - for (RtpSession s : getSystemSupportedAudioSessions()) { - if (s.getCodecId() == codecId) { - return new RtpAudioSession(codecId); - } - } - return null; - } - - static Encoder createEncoder(int id) { - switch (id) { - case 8: - return new G711ACodec(); - default: - return new G711UCodec(); - } - } - - static Decoder createDecoder(int id) { - switch (id) { - case 8: - return new G711ACodec(); - default: - return new G711UCodec(); - } - } -} diff --git a/src/com/android/sip/media/RtpPacket.java b/src/com/android/sip/media/RtpPacket.java deleted file mode 100644 index 60d79f2..0000000 --- a/src/com/android/sip/media/RtpPacket.java +++ /dev/null @@ -1,134 +0,0 @@ -/*
- * Copyright (C) 2009 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 com.android.sip.media;
-
-import java.util.Random;
-
-/**
- * RtpPacket implements a RTP packet.
- */
-public class RtpPacket {
- private static Random sRandom = new Random();
-
- // |0 0 1 2 |
- // |0.......8.......6.......4.......|
- // |V PXC MT Seqnum (16) |
- // |................................|
- // |Timestamp (32) |
- // | |
- // |................................|
- // | SSRC (32) |
- // | |
- // |................................|
- // | CSRC list (16 items x 32 bits) |
- // | |
- // |................................|
- // V: version, 2 bits
- // P: padding, 1 bit
- // X: extension, 1 bit
- // C: CSRC count, 4 bits
- // M: marker, 1 bit
- // T: payload type: 7 bits
-
- private byte[] packet; // RTP header + payload
- private int packetLength;
-
- public RtpPacket(byte[] buffer) {
- packet = buffer;
- setVersion(2);
- setPayloadType(0x0F);
- setSequenceNumber(sRandom.nextInt());
- setSscr(sRandom.nextLong());
- }
-
- /** Returns the RTP packet in raw bytes. */
- public byte[] getRawPacket() {
- return packet;
- }
-
- public int getPacketLength() {
- return packetLength;
- }
-
- public int getHeaderLength() {
- return (12 + 4 * getCscrCount());
- }
-
- public int getPayloadLength() {
- return (packetLength - getHeaderLength());
- }
-
- public void setPayloadLength(int length) {
- packetLength = getHeaderLength() + length;
- }
-
- public int getVersion() {
- return ((packet[0] >> 6) & 0x03);
- }
-
- public void setVersion(int v) {
- if (v > 3) throw new RuntimeException("illegal version: " + v);
- packet[0] = (byte) ((packet[0] & 0x3F) | ((v & 0x03) << 6));
- }
-
- int getCscrCount() {
- return (packet[0] & 0x0F);
- }
-
- public int getPayloadType() {
- return (packet[1] & 0x7F);
- }
-
- public void setPayloadType(int pt) {
- packet[1] = (byte) ((packet[1] & 0x80) | (pt & 0x7F));
- }
-
- public int getSequenceNumber() {
- return (int) get(2, 2);
- }
-
- public void setSequenceNumber(int sn) {
- set((long) sn, 2, 2);
- }
-
- public long getTimestamp() {
- return get(4, 4);
- }
-
- public void setTimestamp(long timestamp) {
- set(timestamp, 4, 4);
- }
-
- void setSscr(long ssrc) {
- set(ssrc, 8, 4);
- }
-
- private long get(int begin, int length) {
- long n = 0;
- for (int i = begin, end = i + length; i < end; i++) {
- n = (n << 8) | ((long) packet[i] & 0xFF);
- }
- return n;
- }
-
- private void set(long n, int begin, int length) {
- for (int i = begin + length - 1; i >= begin; i--) {
- packet[i] = (byte) (n & 0x0FFL);
- n >>= 8;
- }
- }
-}
diff --git a/src/com/android/sip/media/RtpSession.java b/src/com/android/sip/media/RtpSession.java deleted file mode 100644 index b53afad..0000000 --- a/src/com/android/sip/media/RtpSession.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.sip.media; - -import java.io.IOException; -import java.net.DatagramSocket; - -public interface RtpSession { - int getCodecId(); - String getName(); - int getSampleRate(); - void start(int remoteSampleRate, DatagramSocket connectedSocket) - throws IOException ; - void stop(); - void sendDtmf(); -} |