| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.sip; |
| |
| 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.header.extensions.ReferencesHeader; |
| import gov.nist.javax.sip.header.extensions.ReferredByHeader; |
| import gov.nist.javax.sip.header.extensions.ReplacesHeader; |
| |
| import android.net.sip.SipProfile; |
| import android.telephony.Rlog; |
| |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.EventObject; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| |
| 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.TransactionTerminatedEvent; |
| 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 static final boolean DBG = false; |
| private static final boolean DBG_PING = false; |
| |
| 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 ListeningPoint getListeningPoint() throws SipException { |
| ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP); |
| if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP); |
| if (lp == null) { |
| ListeningPoint[] lps = mSipProvider.getListeningPoints(); |
| if ((lps != null) && (lps.length > 0)) lp = lps[0]; |
| } |
| if (lp == null) { |
| throw new SipException("no listening point is available"); |
| } |
| return lp; |
| } |
| |
| private List<ViaHeader> createViaHeaders() |
| throws ParseException, SipException { |
| List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1); |
| ListeningPoint lp = getListeningPoint(); |
| ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(), |
| lp.getPort(), lp.getTransport(), null); |
| viaHeader.setRPort(); |
| viaHeaders.add(viaHeader); |
| return viaHeaders; |
| } |
| |
| private ContactHeader createContactHeader(SipProfile profile) |
| throws ParseException, SipException { |
| return createContactHeader(profile, null, 0); |
| } |
| |
| private ContactHeader createContactHeader(SipProfile profile, |
| String ip, int port) throws ParseException, |
| SipException { |
| SipURI contactURI = (ip == null) |
| ? createSipUri(profile.getUserName(), profile.getProtocol(), |
| getListeningPoint()) |
| : createSipUri(profile.getUserName(), profile.getProtocol(), |
| ip, port); |
| |
| Address contactAddress = mAddressFactory.createAddress(contactURI); |
| contactAddress.setDisplayName(profile.getDisplayName()); |
| |
| return mHeaderFactory.createContactHeader(contactAddress); |
| } |
| |
| private ContactHeader createWildcardContactHeader() { |
| ContactHeader contactHeader = mHeaderFactory.createContactHeader(); |
| contactHeader.setWildCard(); |
| return contactHeader; |
| } |
| |
| private SipURI createSipUri(String username, String transport, |
| ListeningPoint lp) throws ParseException { |
| return createSipUri(username, transport, lp.getIPAddress(), lp.getPort()); |
| } |
| |
| private SipURI createSipUri(String username, String transport, |
| String ip, int port) throws ParseException { |
| SipURI uri = mAddressFactory.createSipURI(username, ip); |
| try { |
| uri.setPort(port); |
| uri.setTransportParam(transport); |
| } catch (InvalidArgumentException e) { |
| throw new RuntimeException(e); |
| } |
| return uri; |
| } |
| |
| public ClientTransaction sendOptions(SipProfile caller, SipProfile callee, |
| String tag) throws SipException { |
| try { |
| Request request = (caller == callee) |
| ? createRequest(Request.OPTIONS, caller, tag) |
| : createRequest(Request.OPTIONS, caller, callee, tag); |
| |
| ClientTransaction clientTransaction = |
| mSipProvider.getNewClientTransaction(request); |
| clientTransaction.sendRequest(); |
| return clientTransaction; |
| } catch (Exception e) { |
| throw new SipException("sendOptions()", e); |
| } |
| } |
| |
| public ClientTransaction sendRegister(SipProfile userProfile, String tag, |
| int expiry) throws SipException { |
| try { |
| Request request = createRequest(Request.REGISTER, userProfile, tag); |
| if (expiry == 0) { |
| // remove all previous registrations by wildcard |
| // rfc3261#section-10.2.2 |
| request.addHeader(createWildcardContactHeader()); |
| } else { |
| request.addHeader(createContactHeader(userProfile)); |
| } |
| request.addHeader(mHeaderFactory.createExpiresHeader(expiry)); |
| |
| ClientTransaction clientTransaction = |
| mSipProvider.getNewClientTransaction(request); |
| clientTransaction.sendRequest(); |
| return clientTransaction; |
| } catch (ParseException e) { |
| throw new SipException("sendRegister()", e); |
| } |
| } |
| |
| private Request createRequest(String requestType, SipProfile userProfile, |
| String tag) throws ParseException, SipException { |
| FromHeader fromHeader = createFromHeader(userProfile, tag); |
| ToHeader toHeader = createToHeader(userProfile); |
| |
| String replaceStr = Pattern.quote(userProfile.getUserName() + "@"); |
| SipURI requestURI = mAddressFactory.createSipURI( |
| userProfile.getUriString().replaceFirst(replaceStr, "")); |
| |
| List<ViaHeader> viaHeaders = createViaHeaders(); |
| CallIdHeader callIdHeader = createCallIdHeader(); |
| CSeqHeader cSeqHeader = createCSeqHeader(requestType); |
| MaxForwardsHeader maxForwards = createMaxForwardsHeader(); |
| Request request = mMessageFactory.createRequest(requestURI, |
| requestType, callIdHeader, cSeqHeader, fromHeader, |
| toHeader, viaHeaders, maxForwards); |
| Header userAgentHeader = mHeaderFactory.createHeader("User-Agent", |
| "SIPAUA/0.1.001"); |
| request.addHeader(userAgentHeader); |
| return request; |
| } |
| |
| public ClientTransaction handleChallenge(ResponseEvent responseEvent, |
| AccountManager accountManager) throws SipException { |
| AuthenticationHelper authenticationHelper = |
| ((SipStackExt) mSipStack).getAuthenticationHelper( |
| accountManager, mHeaderFactory); |
| ClientTransaction tid = responseEvent.getClientTransaction(); |
| ClientTransaction ct = authenticationHelper.handleChallenge( |
| responseEvent.getResponse(), tid, mSipProvider, 5); |
| if (DBG) log("send request with challenge response: " |
| + ct.getRequest()); |
| ct.sendRequest(); |
| return ct; |
| } |
| |
| private Request createRequest(String requestType, SipProfile caller, |
| SipProfile callee, String tag) throws ParseException, SipException { |
| FromHeader fromHeader = createFromHeader(caller, tag); |
| ToHeader toHeader = createToHeader(callee); |
| SipURI requestURI = callee.getUri(); |
| List<ViaHeader> viaHeaders = createViaHeaders(); |
| CallIdHeader callIdHeader = createCallIdHeader(); |
| CSeqHeader cSeqHeader = createCSeqHeader(requestType); |
| MaxForwardsHeader maxForwards = createMaxForwardsHeader(); |
| |
| Request request = mMessageFactory.createRequest(requestURI, |
| requestType, callIdHeader, cSeqHeader, fromHeader, |
| toHeader, viaHeaders, maxForwards); |
| |
| request.addHeader(createContactHeader(caller)); |
| return request; |
| } |
| |
| public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, |
| String sessionDescription, String tag, ReferredByHeader referredBy, |
| String replaces) throws SipException { |
| try { |
| Request request = createRequest(Request.INVITE, caller, callee, tag); |
| if (referredBy != null) request.addHeader(referredBy); |
| if (replaces != null) { |
| request.addHeader(mHeaderFactory.createHeader( |
| ReplacesHeader.NAME, replaces)); |
| } |
| request.setContent(sessionDescription, |
| mHeaderFactory.createContentTypeHeader( |
| "application", "sdp")); |
| ClientTransaction clientTransaction = |
| mSipProvider.getNewClientTransaction(request); |
| if (DBG) log("send INVITE: " + request); |
| clientTransaction.sendRequest(); |
| return clientTransaction; |
| } catch (ParseException e) { |
| throw new SipException("sendInvite()", e); |
| } |
| } |
| |
| public ClientTransaction sendReinvite(Dialog dialog, |
| String sessionDescription) throws SipException { |
| try { |
| Request request = dialog.createRequest(Request.INVITE); |
| request.setContent(sessionDescription, |
| mHeaderFactory.createContentTypeHeader( |
| "application", "sdp")); |
| |
| // Adding rport argument in the request could fix some SIP servers |
| // in resolving the initiator's NAT port mapping for relaying the |
| // response message from the other end. |
| |
| ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); |
| if (viaHeader != null) viaHeader.setRPort(); |
| |
| ClientTransaction clientTransaction = |
| mSipProvider.getNewClientTransaction(request); |
| if (DBG) log("send RE-INVITE: " + request); |
| dialog.sendRequest(clientTransaction); |
| return clientTransaction; |
| } catch (ParseException e) { |
| throw new SipException("sendReinvite()", e); |
| } |
| } |
| |
| public 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, String tag) |
| throws SipException { |
| try { |
| Request request = event.getRequest(); |
| ServerTransaction transaction = getServerTransaction(event); |
| |
| Response response = mMessageFactory.createResponse(Response.RINGING, |
| request); |
| |
| ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); |
| toHeader.setTag(tag); |
| response.addHeader(toHeader); |
| if (DBG) log("send RINGING: " + response); |
| transaction.sendResponse(response); |
| return transaction; |
| } catch (ParseException e) { |
| throw new SipException("sendRinging()", e); |
| } |
| } |
| |
| /** |
| * @param event the INVITE request event |
| */ |
| public ServerTransaction sendInviteOk(RequestEvent event, |
| SipProfile localProfile, String sessionDescription, |
| ServerTransaction inviteTransaction, String externalIp, |
| int externalPort) throws SipException { |
| try { |
| Request request = event.getRequest(); |
| Response response = mMessageFactory.createResponse(Response.OK, |
| request); |
| response.addHeader(createContactHeader(localProfile, externalIp, |
| externalPort)); |
| response.setContent(sessionDescription, |
| mHeaderFactory.createContentTypeHeader( |
| "application", "sdp")); |
| |
| if (inviteTransaction == null) { |
| inviteTransaction = getServerTransaction(event); |
| } |
| |
| if (inviteTransaction.getState() != TransactionState.COMPLETED) { |
| if (DBG) log("send OK: " + response); |
| inviteTransaction.sendResponse(response); |
| } |
| |
| return inviteTransaction; |
| } catch (ParseException e) { |
| throw new SipException("sendInviteOk()", 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 == null) { |
| inviteTransaction = getServerTransaction(event); |
| } |
| |
| if (inviteTransaction.getState() != TransactionState.COMPLETED) { |
| if (DBG) log("send BUSY HERE: " + response); |
| 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(); |
| Request ack = dialog.createAck(cseq); |
| if (DBG) log("send ACK: " + ack); |
| dialog.sendAck(ack); |
| } |
| |
| public void sendBye(Dialog dialog) throws SipException { |
| Request byeRequest = dialog.createRequest(Request.BYE); |
| if (DBG) log("send BYE: " + byeRequest); |
| dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); |
| } |
| |
| public void sendCancel(ClientTransaction inviteTransaction) |
| throws SipException { |
| Request cancelRequest = inviteTransaction.createCancel(); |
| if (DBG) log("send CANCEL: " + cancelRequest); |
| mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); |
| } |
| |
| public void sendResponse(RequestEvent event, int responseCode) |
| throws SipException { |
| try { |
| Request request = event.getRequest(); |
| Response response = mMessageFactory.createResponse( |
| responseCode, request); |
| if (DBG && (!Request.OPTIONS.equals(request.getMethod()) |
| || DBG_PING)) { |
| log("send response: " + response); |
| } |
| getServerTransaction(event).sendResponse(response); |
| } catch (ParseException e) { |
| throw new SipException("sendResponse()", e); |
| } |
| } |
| |
| public void sendReferNotify(Dialog dialog, String content) |
| throws SipException { |
| try { |
| Request request = dialog.createRequest(Request.NOTIFY); |
| request.addHeader(mHeaderFactory.createSubscriptionStateHeader( |
| "active;expires=60")); |
| // set content here |
| request.setContent(content, |
| mHeaderFactory.createContentTypeHeader( |
| "message", "sipfrag")); |
| request.addHeader(mHeaderFactory.createEventHeader( |
| ReferencesHeader.REFER)); |
| if (DBG) log("send NOTIFY: " + request); |
| dialog.sendRequest(mSipProvider.getNewClientTransaction(request)); |
| } catch (ParseException e) { |
| throw new SipException("sendReferNotify()", e); |
| } |
| } |
| |
| public void sendInviteRequestTerminated(Request inviteRequest, |
| ServerTransaction inviteTransaction) throws SipException { |
| try { |
| Response response = mMessageFactory.createResponse( |
| Response.REQUEST_TERMINATED, inviteRequest); |
| if (DBG) log("send response: " + response); |
| inviteTransaction.sendResponse(response); |
| } catch (ParseException e) { |
| throw new SipException("sendInviteRequestTerminated()", e); |
| } |
| } |
| |
| public static String getCallId(EventObject event) { |
| if (event == null) return null; |
| if (event instanceof RequestEvent) { |
| return getCallId(((RequestEvent) event).getRequest()); |
| } else if (event instanceof ResponseEvent) { |
| return getCallId(((ResponseEvent) event).getResponse()); |
| } else if (event instanceof DialogTerminatedEvent) { |
| Dialog dialog = ((DialogTerminatedEvent) event).getDialog(); |
| return getCallId(((DialogTerminatedEvent) event).getDialog()); |
| } else if (event instanceof TransactionTerminatedEvent) { |
| TransactionTerminatedEvent e = (TransactionTerminatedEvent) event; |
| return getCallId(e.isServerTransaction() |
| ? e.getServerTransaction() |
| : e.getClientTransaction()); |
| } else { |
| Object source = event.getSource(); |
| if (source instanceof Transaction) { |
| return getCallId(((Transaction) source)); |
| } else if (source instanceof Dialog) { |
| return getCallId((Dialog) source); |
| } |
| } |
| return ""; |
| } |
| |
| public static String getCallId(Transaction transaction) { |
| return ((transaction != null) ? getCallId(transaction.getRequest()) |
| : ""); |
| } |
| |
| private static String getCallId(Message message) { |
| CallIdHeader callIdHeader = |
| (CallIdHeader) message.getHeader(CallIdHeader.NAME); |
| return callIdHeader.getCallId(); |
| } |
| |
| private static String getCallId(Dialog dialog) { |
| return dialog.getCallId().getCallId(); |
| } |
| |
| private void log(String s) { |
| Rlog.d(TAG, s); |
| } |
| } |