SipSessionGroup.java revision 7e54ef71db3320a751571bba5259fba816399421
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.sip; 18 19import gov.nist.javax.sip.clientauthutils.AccountManager; 20import gov.nist.javax.sip.clientauthutils.UserCredentials; 21import gov.nist.javax.sip.header.SIPHeaderNames; 22import gov.nist.javax.sip.header.ProxyAuthenticate; 23import gov.nist.javax.sip.header.WWWAuthenticate; 24import gov.nist.javax.sip.message.SIPMessage; 25 26import android.net.sip.ISipSession; 27import android.net.sip.ISipSessionListener; 28import android.net.sip.SipErrorCode; 29import android.net.sip.SipProfile; 30import android.net.sip.SipSession; 31import android.net.sip.SipSessionAdapter; 32import android.text.TextUtils; 33import android.util.Log; 34 35import java.io.IOException; 36import java.io.UnsupportedEncodingException; 37import java.net.DatagramSocket; 38import java.net.UnknownHostException; 39import java.text.ParseException; 40import java.util.Collection; 41import java.util.EventObject; 42import java.util.HashMap; 43import java.util.Map; 44import java.util.Properties; 45import java.util.TooManyListenersException; 46 47import javax.sip.ClientTransaction; 48import javax.sip.Dialog; 49import javax.sip.DialogTerminatedEvent; 50import javax.sip.IOExceptionEvent; 51import javax.sip.InvalidArgumentException; 52import javax.sip.ListeningPoint; 53import javax.sip.RequestEvent; 54import javax.sip.ResponseEvent; 55import javax.sip.ServerTransaction; 56import javax.sip.SipException; 57import javax.sip.SipFactory; 58import javax.sip.SipListener; 59import javax.sip.SipProvider; 60import javax.sip.SipStack; 61import javax.sip.TimeoutEvent; 62import javax.sip.Transaction; 63import javax.sip.TransactionState; 64import javax.sip.TransactionTerminatedEvent; 65import javax.sip.TransactionUnavailableException; 66import javax.sip.address.Address; 67import javax.sip.address.SipURI; 68import javax.sip.header.CSeqHeader; 69import javax.sip.header.ExpiresHeader; 70import javax.sip.header.FromHeader; 71import javax.sip.header.MinExpiresHeader; 72import javax.sip.header.ViaHeader; 73import javax.sip.message.Message; 74import javax.sip.message.Request; 75import javax.sip.message.Response; 76 77/** 78 * Manages {@link ISipSession}'s for a SIP account. 79 */ 80class SipSessionGroup implements SipListener { 81 private static final String TAG = "SipSession"; 82 private static final boolean DEBUG = true; 83 private static final boolean DEBUG_PING = DEBUG && false; 84 private static final String ANONYMOUS = "anonymous"; 85 private static final String SERVER_ERROR_PREFIX = "Response: "; 86 private static final int EXPIRY_TIME = 3600; // in seconds 87 private static final int CANCEL_CALL_TIMER = 3; // in seconds 88 89 private static final EventObject DEREGISTER = new EventObject("Deregister"); 90 private static final EventObject END_CALL = new EventObject("End call"); 91 private static final EventObject HOLD_CALL = new EventObject("Hold call"); 92 private static final EventObject CONTINUE_CALL 93 = new EventObject("Continue call"); 94 95 private final SipProfile mLocalProfile; 96 private final String mPassword; 97 98 private SipStack mSipStack; 99 private SipHelper mSipHelper; 100 private String mLastNonce; 101 private int mRPort; 102 103 // session that processes INVITE requests 104 private SipSessionImpl mCallReceiverSession; 105 private String mLocalIp; 106 107 // call-id-to-SipSession map 108 private Map<String, SipSessionImpl> mSessionMap = 109 new HashMap<String, SipSessionImpl>(); 110 111 /** 112 * @param myself the local profile with password crossed out 113 * @param password the password of the profile 114 * @throws IOException if cannot assign requested address 115 */ 116 public SipSessionGroup(String localIp, SipProfile myself, String password) 117 throws SipException, IOException { 118 mLocalProfile = myself; 119 mPassword = password; 120 reset(localIp); 121 } 122 123 synchronized void reset(String localIp) throws SipException, IOException { 124 mLocalIp = localIp; 125 if (localIp == null) return; 126 127 SipProfile myself = mLocalProfile; 128 SipFactory sipFactory = SipFactory.getInstance(); 129 Properties properties = new Properties(); 130 properties.setProperty("javax.sip.STACK_NAME", getStackName()); 131 String outboundProxy = myself.getProxyAddress(); 132 if (!TextUtils.isEmpty(outboundProxy)) { 133 Log.v(TAG, "outboundProxy is " + outboundProxy); 134 properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy 135 + ":" + myself.getPort() + "/" + myself.getProtocol()); 136 } 137 SipStack stack = mSipStack = sipFactory.createSipStack(properties); 138 139 try { 140 SipProvider provider = stack.createSipProvider( 141 stack.createListeningPoint(localIp, allocateLocalPort(), 142 myself.getProtocol())); 143 provider.addSipListener(this); 144 mSipHelper = new SipHelper(stack, provider); 145 } catch (InvalidArgumentException e) { 146 throw new IOException(e.getMessage()); 147 } catch (TooManyListenersException e) { 148 // must never happen 149 throw new SipException("SipSessionGroup constructor", e); 150 } 151 Log.d(TAG, " start stack for " + myself.getUriString()); 152 stack.start(); 153 154 mLastNonce = null; 155 mCallReceiverSession = null; 156 mSessionMap.clear(); 157 } 158 159 synchronized void onConnectivityChanged() { 160 for (SipSessionImpl s : mSessionMap.values()) { 161 s.onError(SipErrorCode.DATA_CONNECTION_LOST, 162 "data connection lost"); 163 } 164 } 165 166 public SipProfile getLocalProfile() { 167 return mLocalProfile; 168 } 169 170 public String getLocalProfileUri() { 171 return mLocalProfile.getUriString(); 172 } 173 174 private String getStackName() { 175 return "stack" + System.currentTimeMillis(); 176 } 177 178 public synchronized void close() { 179 Log.d(TAG, " close stack for " + mLocalProfile.getUriString()); 180 mSessionMap.clear(); 181 closeToNotReceiveCalls(); 182 if (mSipStack != null) { 183 mSipStack.stop(); 184 mSipStack = null; 185 mSipHelper = null; 186 } 187 } 188 189 public synchronized boolean isClosed() { 190 return (mSipStack == null); 191 } 192 193 // For internal use, require listener not to block in callbacks. 194 public synchronized void openToReceiveCalls(ISipSessionListener listener) { 195 if (mCallReceiverSession == null) { 196 mCallReceiverSession = new SipSessionCallReceiverImpl(listener); 197 } else { 198 mCallReceiverSession.setListener(listener); 199 } 200 } 201 202 public synchronized void closeToNotReceiveCalls() { 203 mCallReceiverSession = null; 204 } 205 206 public ISipSession createSession(ISipSessionListener listener) { 207 return (isClosed() ? null : new SipSessionImpl(listener)); 208 } 209 210 private static int allocateLocalPort() throws SipException { 211 try { 212 DatagramSocket s = new DatagramSocket(); 213 int localPort = s.getLocalPort(); 214 s.close(); 215 return localPort; 216 } catch (IOException e) { 217 throw new SipException("allocateLocalPort()", e); 218 } 219 } 220 221 private synchronized SipSessionImpl getSipSession(EventObject event) { 222 String key = SipHelper.getCallId(event); 223 SipSessionImpl session = mSessionMap.get(key); 224 if ((session != null) && isLoggable(session)) { 225 Log.d(TAG, "session key from event: " + key); 226 Log.d(TAG, "active sessions:"); 227 for (String k : mSessionMap.keySet()) { 228 Log.d(TAG, " ..." + k + ": " + mSessionMap.get(k)); 229 } 230 } 231 return ((session != null) ? session : mCallReceiverSession); 232 } 233 234 private synchronized void addSipSession(SipSessionImpl newSession) { 235 removeSipSession(newSession); 236 String key = newSession.getCallId(); 237 mSessionMap.put(key, newSession); 238 if (isLoggable(newSession)) { 239 Log.d(TAG, "+++ add a session with key: '" + key + "'"); 240 for (String k : mSessionMap.keySet()) { 241 Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); 242 } 243 } 244 } 245 246 private synchronized void removeSipSession(SipSessionImpl session) { 247 if (session == mCallReceiverSession) return; 248 String key = session.getCallId(); 249 SipSessionImpl s = mSessionMap.remove(key); 250 // sanity check 251 if ((s != null) && (s != session)) { 252 Log.w(TAG, "session " + session + " is not associated with key '" 253 + key + "'"); 254 mSessionMap.put(key, s); 255 for (Map.Entry<String, SipSessionImpl> entry 256 : mSessionMap.entrySet()) { 257 if (entry.getValue() == s) { 258 key = entry.getKey(); 259 mSessionMap.remove(key); 260 } 261 } 262 } 263 264 if ((s != null) && isLoggable(s)) { 265 Log.d(TAG, "remove session " + session + " @key '" + key + "'"); 266 for (String k : mSessionMap.keySet()) { 267 Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); 268 } 269 } 270 } 271 272 public void processRequest(RequestEvent event) { 273 process(event); 274 } 275 276 public void processResponse(ResponseEvent event) { 277 process(event); 278 } 279 280 public void processIOException(IOExceptionEvent event) { 281 process(event); 282 } 283 284 public void processTimeout(TimeoutEvent event) { 285 process(event); 286 } 287 288 public void processTransactionTerminated(TransactionTerminatedEvent event) { 289 process(event); 290 } 291 292 public void processDialogTerminated(DialogTerminatedEvent event) { 293 process(event); 294 } 295 296 private synchronized void process(EventObject event) { 297 SipSessionImpl session = getSipSession(event); 298 try { 299 boolean isLoggable = isLoggable(session, event); 300 boolean processed = (session != null) && session.process(event); 301 if (isLoggable && processed) { 302 Log.d(TAG, "new state after: " 303 + SipSession.State.toString(session.mState)); 304 } 305 } catch (Throwable e) { 306 Log.w(TAG, "event process error: " + event, e); 307 session.onError(e); 308 } 309 } 310 311 private String extractContent(Message message) { 312 // Currently we do not support secure MIME bodies. 313 byte[] bytes = message.getRawContent(); 314 if (bytes != null) { 315 try { 316 if (message instanceof SIPMessage) { 317 return ((SIPMessage) message).getMessageContent(); 318 } else { 319 return new String(bytes, "UTF-8"); 320 } 321 } catch (UnsupportedEncodingException e) { 322 } 323 } 324 return null; 325 } 326 327 private class SipSessionCallReceiverImpl extends SipSessionImpl { 328 public SipSessionCallReceiverImpl(ISipSessionListener listener) { 329 super(listener); 330 } 331 332 public boolean process(EventObject evt) throws SipException { 333 if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " 334 + SipSession.State.toString(mState) + ": processing " 335 + log(evt)); 336 if (isRequestEvent(Request.INVITE, evt)) { 337 RequestEvent event = (RequestEvent) evt; 338 SipSessionImpl newSession = new SipSessionImpl(mProxy); 339 newSession.mServerTransaction = mSipHelper.sendRinging(event, 340 generateTag()); 341 newSession.mDialog = newSession.mServerTransaction.getDialog(); 342 newSession.mInviteReceived = event; 343 newSession.mPeerProfile = createPeerProfile(event.getRequest()); 344 newSession.mState = SipSession.State.INCOMING_CALL; 345 newSession.mPeerSessionDescription = 346 extractContent(event.getRequest()); 347 addSipSession(newSession); 348 mProxy.onRinging(newSession, newSession.mPeerProfile, 349 newSession.mPeerSessionDescription); 350 return true; 351 } else if (isRequestEvent(Request.OPTIONS, evt)) { 352 mSipHelper.sendResponse((RequestEvent) evt, Response.OK); 353 return true; 354 } else { 355 return false; 356 } 357 } 358 } 359 360 class SipSessionImpl extends ISipSession.Stub { 361 SipProfile mPeerProfile; 362 SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); 363 int mState = SipSession.State.READY_TO_CALL; 364 RequestEvent mInviteReceived; 365 Dialog mDialog; 366 ServerTransaction mServerTransaction; 367 ClientTransaction mClientTransaction; 368 String mPeerSessionDescription; 369 boolean mInCall; 370 boolean mReRegisterFlag = false; 371 SessionTimer mTimer; 372 373 // lightweight timer 374 class SessionTimer { 375 private boolean mRunning = true; 376 377 void start(final int timeout) { 378 new Thread(new Runnable() { 379 public void run() { 380 sleep(timeout); 381 if (mRunning) timeout(); 382 } 383 }, "SipSessionTimerThread").start(); 384 } 385 386 synchronized void cancel() { 387 mRunning = false; 388 this.notify(); 389 } 390 391 private void timeout() { 392 synchronized (SipSessionGroup.this) { 393 onError(SipErrorCode.TIME_OUT, "Session timed out!"); 394 } 395 } 396 397 private synchronized void sleep(int timeout) { 398 try { 399 this.wait(timeout * 1000); 400 } catch (InterruptedException e) { 401 Log.e(TAG, "session timer interrupted!"); 402 } 403 } 404 } 405 406 public SipSessionImpl(ISipSessionListener listener) { 407 setListener(listener); 408 } 409 410 SipSessionImpl duplicate() { 411 return new SipSessionImpl(mProxy.getListener()); 412 } 413 414 private void reset() { 415 mInCall = false; 416 removeSipSession(this); 417 mPeerProfile = null; 418 mState = SipSession.State.READY_TO_CALL; 419 mInviteReceived = null; 420 mDialog = null; 421 mServerTransaction = null; 422 mClientTransaction = null; 423 mPeerSessionDescription = null; 424 425 cancelSessionTimer(); 426 } 427 428 public boolean isInCall() { 429 return mInCall; 430 } 431 432 public String getLocalIp() { 433 return mLocalIp; 434 } 435 436 public SipProfile getLocalProfile() { 437 return mLocalProfile; 438 } 439 440 public SipProfile getPeerProfile() { 441 return mPeerProfile; 442 } 443 444 public String getCallId() { 445 return SipHelper.getCallId(getTransaction()); 446 } 447 448 private Transaction getTransaction() { 449 if (mClientTransaction != null) return mClientTransaction; 450 if (mServerTransaction != null) return mServerTransaction; 451 return null; 452 } 453 454 public int getState() { 455 return mState; 456 } 457 458 public void setListener(ISipSessionListener listener) { 459 mProxy.setListener((listener instanceof SipSessionListenerProxy) 460 ? ((SipSessionListenerProxy) listener).getListener() 461 : listener); 462 } 463 464 // process the command in a new thread 465 private void doCommandAsync(final EventObject command) { 466 new Thread(new Runnable() { 467 public void run() { 468 try { 469 processCommand(command); 470 } catch (SipException e) { 471 Log.w(TAG, "command error: " + command, e); 472 onError(e); 473 } 474 } 475 }, "SipSessionAsyncCmdThread").start(); 476 } 477 478 public void makeCall(SipProfile peerProfile, String sessionDescription, 479 int timeout) { 480 doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription, 481 timeout)); 482 } 483 484 public void answerCall(String sessionDescription, int timeout) { 485 try { 486 processCommand(new MakeCallCommand(mPeerProfile, 487 sessionDescription, timeout)); 488 } catch (SipException e) { 489 onError(e); 490 } 491 } 492 493 public void endCall() { 494 doCommandAsync(END_CALL); 495 } 496 497 public void changeCall(String sessionDescription, int timeout) { 498 doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, 499 timeout)); 500 } 501 502 public void changeCallWithTimeout( 503 String sessionDescription, int timeout) { 504 doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, 505 timeout)); 506 } 507 508 public void register(int duration) { 509 doCommandAsync(new RegisterCommand(duration)); 510 } 511 512 public void unregister() { 513 doCommandAsync(DEREGISTER); 514 } 515 516 public boolean isReRegisterRequired() { 517 return mReRegisterFlag; 518 } 519 520 public void clearReRegisterRequired() { 521 mReRegisterFlag = false; 522 } 523 524 public void sendKeepAlive() { 525 mState = SipSession.State.PINGING; 526 try { 527 processCommand(new OptionsCommand()); 528 while (SipSession.State.PINGING == mState) { 529 Thread.sleep(1000); 530 } 531 } catch (SipException e) { 532 Log.e(TAG, "sendKeepAlive failed", e); 533 } catch (InterruptedException e) { 534 Log.e(TAG, "sendKeepAlive interrupted", e); 535 } 536 } 537 538 private void processCommand(EventObject command) throws SipException { 539 if (!process(command)) { 540 onError(SipErrorCode.IN_PROGRESS, 541 "cannot initiate a new transaction to execute: " 542 + command); 543 } 544 } 545 546 protected String generateTag() { 547 // 32-bit randomness 548 return String.valueOf((long) (Math.random() * 0x100000000L)); 549 } 550 551 public String toString() { 552 try { 553 String s = super.toString(); 554 return s.substring(s.indexOf("@")) + ":" 555 + SipSession.State.toString(mState); 556 } catch (Throwable e) { 557 return super.toString(); 558 } 559 } 560 561 public boolean process(EventObject evt) throws SipException { 562 if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " 563 + SipSession.State.toString(mState) + ": processing " 564 + log(evt)); 565 synchronized (SipSessionGroup.this) { 566 if (isClosed()) return false; 567 568 Dialog dialog = null; 569 if (evt instanceof RequestEvent) { 570 dialog = ((RequestEvent) evt).getDialog(); 571 } else if (evt instanceof ResponseEvent) { 572 dialog = ((ResponseEvent) evt).getDialog(); 573 } 574 if (dialog != null) mDialog = dialog; 575 576 boolean processed; 577 578 switch (mState) { 579 case SipSession.State.REGISTERING: 580 case SipSession.State.DEREGISTERING: 581 processed = registeringToReady(evt); 582 break; 583 case SipSession.State.PINGING: 584 processed = keepAliveProcess(evt); 585 break; 586 case SipSession.State.READY_TO_CALL: 587 processed = readyForCall(evt); 588 break; 589 case SipSession.State.INCOMING_CALL: 590 processed = incomingCall(evt); 591 break; 592 case SipSession.State.INCOMING_CALL_ANSWERING: 593 processed = incomingCallToInCall(evt); 594 break; 595 case SipSession.State.OUTGOING_CALL: 596 case SipSession.State.OUTGOING_CALL_RING_BACK: 597 processed = outgoingCall(evt); 598 break; 599 case SipSession.State.OUTGOING_CALL_CANCELING: 600 processed = outgoingCallToReady(evt); 601 break; 602 case SipSession.State.IN_CALL: 603 processed = inCall(evt); 604 break; 605 default: 606 processed = false; 607 } 608 return (processed || processExceptions(evt)); 609 } 610 } 611 612 private boolean processExceptions(EventObject evt) throws SipException { 613 if (isRequestEvent(Request.BYE, evt)) { 614 // terminate the call whenever a BYE is received 615 mSipHelper.sendResponse((RequestEvent) evt, Response.OK); 616 endCallNormally(); 617 return true; 618 } else if (isRequestEvent(Request.CANCEL, evt)) { 619 mSipHelper.sendResponse((RequestEvent) evt, 620 Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); 621 return true; 622 } else if (evt instanceof TransactionTerminatedEvent) { 623 if (isCurrentTransaction((TransactionTerminatedEvent) evt)) { 624 if (evt instanceof TimeoutEvent) { 625 processTimeout((TimeoutEvent) evt); 626 } else { 627 processTransactionTerminated( 628 (TransactionTerminatedEvent) evt); 629 } 630 return true; 631 } 632 } else if (isRequestEvent(Request.OPTIONS, evt)) { 633 mSipHelper.sendResponse((RequestEvent) evt, Response.OK); 634 return true; 635 } else if (evt instanceof DialogTerminatedEvent) { 636 processDialogTerminated((DialogTerminatedEvent) evt); 637 return true; 638 } 639 return false; 640 } 641 642 private void processDialogTerminated(DialogTerminatedEvent event) { 643 if (mDialog == event.getDialog()) { 644 onError(new SipException("dialog terminated")); 645 } else { 646 Log.d(TAG, "not the current dialog; current=" + mDialog 647 + ", terminated=" + event.getDialog()); 648 } 649 } 650 651 private boolean isCurrentTransaction(TransactionTerminatedEvent event) { 652 Transaction current = event.isServerTransaction() 653 ? mServerTransaction 654 : mClientTransaction; 655 Transaction target = event.isServerTransaction() 656 ? event.getServerTransaction() 657 : event.getClientTransaction(); 658 659 if ((current != target) && (mState != SipSession.State.PINGING)) { 660 Log.d(TAG, "not the current transaction; current=" 661 + toString(current) + ", target=" + toString(target)); 662 return false; 663 } else if (current != null) { 664 Log.d(TAG, "transaction terminated: " + toString(current)); 665 return true; 666 } else { 667 // no transaction; shouldn't be here; ignored 668 return true; 669 } 670 } 671 672 private String toString(Transaction transaction) { 673 if (transaction == null) return "null"; 674 Request request = transaction.getRequest(); 675 Dialog dialog = transaction.getDialog(); 676 CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME); 677 return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(), 678 cseq.getSeqNumber(), transaction.getState(), 679 ((dialog == null) ? "-" : dialog.getState())); 680 } 681 682 private void processTransactionTerminated( 683 TransactionTerminatedEvent event) { 684 switch (mState) { 685 case SipSession.State.IN_CALL: 686 case SipSession.State.READY_TO_CALL: 687 Log.d(TAG, "Transaction terminated; do nothing"); 688 break; 689 default: 690 Log.d(TAG, "Transaction terminated early: " + this); 691 onError(SipErrorCode.TRANSACTION_TERMINTED, 692 "transaction terminated"); 693 } 694 } 695 696 private void processTimeout(TimeoutEvent event) { 697 Log.d(TAG, "processing Timeout..."); 698 switch (mState) { 699 case SipSession.State.REGISTERING: 700 case SipSession.State.DEREGISTERING: 701 reset(); 702 mProxy.onRegistrationTimeout(this); 703 break; 704 case SipSession.State.INCOMING_CALL: 705 case SipSession.State.INCOMING_CALL_ANSWERING: 706 case SipSession.State.OUTGOING_CALL: 707 case SipSession.State.OUTGOING_CALL_CANCELING: 708 onError(SipErrorCode.TIME_OUT, event.toString()); 709 break; 710 case SipSession.State.PINGING: 711 reset(); 712 mReRegisterFlag = true; 713 mState = SipSession.State.READY_TO_CALL; 714 break; 715 716 default: 717 Log.d(TAG, " do nothing"); 718 break; 719 } 720 } 721 722 private int getExpiryTime(Response response) { 723 int expires = EXPIRY_TIME; 724 ExpiresHeader expiresHeader = (ExpiresHeader) 725 response.getHeader(ExpiresHeader.NAME); 726 if (expiresHeader != null) expires = expiresHeader.getExpires(); 727 expiresHeader = (ExpiresHeader) 728 response.getHeader(MinExpiresHeader.NAME); 729 if (expiresHeader != null) { 730 expires = Math.max(expires, expiresHeader.getExpires()); 731 } 732 return expires; 733 } 734 735 private boolean keepAliveProcess(EventObject evt) throws SipException { 736 if (evt instanceof OptionsCommand) { 737 mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile, 738 generateTag()); 739 mDialog = mClientTransaction.getDialog(); 740 addSipSession(this); 741 return true; 742 } else if (evt instanceof ResponseEvent) { 743 return parseOptionsResult(evt); 744 } 745 return false; 746 } 747 748 private boolean parseOptionsResult(EventObject evt) { 749 if (expectResponse(Request.OPTIONS, evt)) { 750 ResponseEvent event = (ResponseEvent) evt; 751 int rPort = getRPortFromResponse(event.getResponse()); 752 if (rPort != -1) { 753 if (mRPort == 0) mRPort = rPort; 754 if (mRPort != rPort) { 755 mReRegisterFlag = true; 756 if (DEBUG) Log.w(TAG, String.format( 757 "rport is changed: %d <> %d", mRPort, rPort)); 758 mRPort = rPort; 759 } else { 760 if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort); 761 } 762 } else { 763 if (DEBUG) Log.w(TAG, "peer did not respond rport"); 764 } 765 reset(); 766 return true; 767 } 768 return false; 769 } 770 771 private int getRPortFromResponse(Response response) { 772 ViaHeader viaHeader = (ViaHeader)(response.getHeader( 773 SIPHeaderNames.VIA)); 774 return (viaHeader == null) ? -1 : viaHeader.getRPort(); 775 } 776 777 private boolean registeringToReady(EventObject evt) 778 throws SipException { 779 if (expectResponse(Request.REGISTER, evt)) { 780 ResponseEvent event = (ResponseEvent) evt; 781 Response response = event.getResponse(); 782 783 int statusCode = response.getStatusCode(); 784 switch (statusCode) { 785 case Response.OK: 786 int state = mState; 787 onRegistrationDone((state == SipSession.State.REGISTERING) 788 ? getExpiryTime(((ResponseEvent) evt).getResponse()) 789 : -1); 790 mLastNonce = null; 791 mRPort = 0; 792 return true; 793 case Response.UNAUTHORIZED: 794 case Response.PROXY_AUTHENTICATION_REQUIRED: 795 if (!handleAuthentication(event)) { 796 if (mLastNonce == null) { 797 onRegistrationFailed(SipErrorCode.SERVER_ERROR, 798 "server does not provide challenge"); 799 } else { 800 Log.v(TAG, "Incorrect username/password"); 801 onRegistrationFailed( 802 SipErrorCode.INVALID_CREDENTIALS, 803 "incorrect username or password"); 804 } 805 } 806 return true; 807 default: 808 if (statusCode >= 500) { 809 onRegistrationFailed(response); 810 return true; 811 } 812 } 813 } 814 return false; 815 } 816 817 private boolean handleAuthentication(ResponseEvent event) 818 throws SipException { 819 Response response = event.getResponse(); 820 String nonce = getNonceFromResponse(response); 821 if (((nonce != null) && nonce.equals(mLastNonce)) || 822 (nonce == null)) { 823 mLastNonce = nonce; 824 return false; 825 } else { 826 mClientTransaction = mSipHelper.handleChallenge( 827 event, getAccountManager()); 828 mDialog = mClientTransaction.getDialog(); 829 mLastNonce = nonce; 830 return true; 831 } 832 } 833 834 private boolean crossDomainAuthenticationRequired(Response response) { 835 String realm = getRealmFromResponse(response); 836 if (realm == null) realm = ""; 837 return !mLocalProfile.getSipDomain().trim().equals(realm.trim()); 838 } 839 840 private AccountManager getAccountManager() { 841 return new AccountManager() { 842 public UserCredentials getCredentials(ClientTransaction 843 challengedTransaction, String realm) { 844 return new UserCredentials() { 845 public String getUserName() { 846 return mLocalProfile.getUserName(); 847 } 848 849 public String getPassword() { 850 return mPassword; 851 } 852 853 public String getSipDomain() { 854 return mLocalProfile.getSipDomain(); 855 } 856 }; 857 } 858 }; 859 } 860 861 private String getRealmFromResponse(Response response) { 862 WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( 863 SIPHeaderNames.WWW_AUTHENTICATE); 864 if (wwwAuth != null) return wwwAuth.getRealm(); 865 ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( 866 SIPHeaderNames.PROXY_AUTHENTICATE); 867 return (proxyAuth == null) ? null : proxyAuth.getRealm(); 868 } 869 870 private String getNonceFromResponse(Response response) { 871 WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( 872 SIPHeaderNames.WWW_AUTHENTICATE); 873 if (wwwAuth != null) return wwwAuth.getNonce(); 874 ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( 875 SIPHeaderNames.PROXY_AUTHENTICATE); 876 return (proxyAuth == null) ? null : proxyAuth.getNonce(); 877 } 878 879 private boolean readyForCall(EventObject evt) throws SipException { 880 // expect MakeCallCommand, RegisterCommand, DEREGISTER 881 if (evt instanceof MakeCallCommand) { 882 MakeCallCommand cmd = (MakeCallCommand) evt; 883 mPeerProfile = cmd.getPeerProfile(); 884 mClientTransaction = mSipHelper.sendInvite(mLocalProfile, 885 mPeerProfile, cmd.getSessionDescription(), 886 generateTag()); 887 mDialog = mClientTransaction.getDialog(); 888 addSipSession(this); 889 mState = SipSession.State.OUTGOING_CALL; 890 mProxy.onCalling(this); 891 startSessionTimer(cmd.getTimeout()); 892 return true; 893 } else if (evt instanceof RegisterCommand) { 894 int duration = ((RegisterCommand) evt).getDuration(); 895 mClientTransaction = mSipHelper.sendRegister(mLocalProfile, 896 generateTag(), duration); 897 mDialog = mClientTransaction.getDialog(); 898 addSipSession(this); 899 mState = SipSession.State.REGISTERING; 900 mProxy.onRegistering(this); 901 return true; 902 } else if (DEREGISTER == evt) { 903 mClientTransaction = mSipHelper.sendRegister(mLocalProfile, 904 generateTag(), 0); 905 mDialog = mClientTransaction.getDialog(); 906 addSipSession(this); 907 mState = SipSession.State.DEREGISTERING; 908 mProxy.onRegistering(this); 909 return true; 910 } 911 return false; 912 } 913 914 private boolean incomingCall(EventObject evt) throws SipException { 915 // expect MakeCallCommand(answering) , END_CALL cmd , Cancel 916 if (evt instanceof MakeCallCommand) { 917 // answer call 918 mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, 919 mLocalProfile, 920 ((MakeCallCommand) evt).getSessionDescription(), 921 mServerTransaction); 922 mState = SipSession.State.INCOMING_CALL_ANSWERING; 923 startSessionTimer(((MakeCallCommand) evt).getTimeout()); 924 return true; 925 } else if (END_CALL == evt) { 926 mSipHelper.sendInviteBusyHere(mInviteReceived, 927 mServerTransaction); 928 endCallNormally(); 929 return true; 930 } else if (isRequestEvent(Request.CANCEL, evt)) { 931 RequestEvent event = (RequestEvent) evt; 932 mSipHelper.sendResponse(event, Response.OK); 933 mSipHelper.sendInviteRequestTerminated( 934 mInviteReceived.getRequest(), mServerTransaction); 935 endCallNormally(); 936 return true; 937 } 938 return false; 939 } 940 941 private boolean incomingCallToInCall(EventObject evt) 942 throws SipException { 943 // expect ACK, CANCEL request 944 if (isRequestEvent(Request.ACK, evt)) { 945 establishCall(); 946 return true; 947 } else if (isRequestEvent(Request.CANCEL, evt)) { 948 // http://tools.ietf.org/html/rfc3261#section-9.2 949 // Final response has been sent; do nothing here. 950 return true; 951 } 952 return false; 953 } 954 955 private boolean outgoingCall(EventObject evt) throws SipException { 956 if (expectResponse(Request.INVITE, evt)) { 957 ResponseEvent event = (ResponseEvent) evt; 958 Response response = event.getResponse(); 959 960 int statusCode = response.getStatusCode(); 961 switch (statusCode) { 962 case Response.RINGING: 963 if (mState == SipSession.State.OUTGOING_CALL) { 964 mState = SipSession.State.OUTGOING_CALL_RING_BACK; 965 mProxy.onRingingBack(this); 966 cancelSessionTimer(); 967 } 968 return true; 969 case Response.OK: 970 mSipHelper.sendInviteAck(event, mDialog); 971 mPeerSessionDescription = extractContent(response); 972 establishCall(); 973 return true; 974 case Response.UNAUTHORIZED: 975 case Response.PROXY_AUTHENTICATION_REQUIRED: 976 if (crossDomainAuthenticationRequired(response)) { 977 onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, 978 getRealmFromResponse(response)); 979 } else if (handleAuthentication(event)) { 980 addSipSession(this); 981 } else if (mLastNonce == null) { 982 onError(SipErrorCode.SERVER_ERROR, 983 "server does not provide challenge"); 984 } else { 985 onError(SipErrorCode.INVALID_CREDENTIALS, 986 "incorrect username or password"); 987 } 988 return true; 989 case Response.REQUEST_PENDING: 990 // TODO: 991 // rfc3261#section-14.1; re-schedule invite 992 return true; 993 default: 994 if (statusCode >= 400) { 995 // error: an ack is sent automatically by the stack 996 onError(response); 997 return true; 998 } else if (statusCode >= 300) { 999 // TODO: handle 3xx (redirect) 1000 } else { 1001 return true; 1002 } 1003 } 1004 return false; 1005 } else if (END_CALL == evt) { 1006 // RFC says that UA should not send out cancel when no 1007 // response comes back yet. We are cheating for not checking 1008 // response. 1009 mSipHelper.sendCancel(mClientTransaction); 1010 mState = SipSession.State.OUTGOING_CALL_CANCELING; 1011 startSessionTimer(CANCEL_CALL_TIMER); 1012 return true; 1013 } 1014 return false; 1015 } 1016 1017 private boolean outgoingCallToReady(EventObject evt) 1018 throws SipException { 1019 if (evt instanceof ResponseEvent) { 1020 ResponseEvent event = (ResponseEvent) evt; 1021 Response response = event.getResponse(); 1022 int statusCode = response.getStatusCode(); 1023 if (expectResponse(Request.CANCEL, evt)) { 1024 if (statusCode == Response.OK) { 1025 // do nothing; wait for REQUEST_TERMINATED 1026 return true; 1027 } 1028 } else if (expectResponse(Request.INVITE, evt)) { 1029 switch (statusCode) { 1030 case Response.OK: 1031 outgoingCall(evt); // abort Cancel 1032 return true; 1033 case Response.REQUEST_TERMINATED: 1034 endCallNormally(); 1035 return true; 1036 } 1037 } else { 1038 return false; 1039 } 1040 1041 if (statusCode >= 400) { 1042 onError(response); 1043 return true; 1044 } 1045 } else if (evt instanceof TransactionTerminatedEvent) { 1046 // rfc3261#section-14.1: 1047 // if re-invite gets timed out, terminate the dialog; but 1048 // re-invite is not reliable, just let it go and pretend 1049 // nothing happened. 1050 onError(new SipException("timed out")); 1051 } 1052 return false; 1053 } 1054 1055 private boolean inCall(EventObject evt) throws SipException { 1056 // expect END_CALL cmd, BYE request, hold call (MakeCallCommand) 1057 // OK retransmission is handled in SipStack 1058 if (END_CALL == evt) { 1059 // rfc3261#section-15.1.1 1060 mSipHelper.sendBye(mDialog); 1061 endCallNormally(); 1062 return true; 1063 } else if (isRequestEvent(Request.INVITE, evt)) { 1064 // got Re-INVITE 1065 RequestEvent event = mInviteReceived = (RequestEvent) evt; 1066 mState = SipSession.State.INCOMING_CALL; 1067 mPeerSessionDescription = extractContent(event.getRequest()); 1068 mServerTransaction = null; 1069 mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription); 1070 return true; 1071 } else if (isRequestEvent(Request.BYE, evt)) { 1072 mSipHelper.sendResponse((RequestEvent) evt, Response.OK); 1073 endCallNormally(); 1074 return true; 1075 } else if (evt instanceof MakeCallCommand) { 1076 // to change call 1077 mClientTransaction = mSipHelper.sendReinvite(mDialog, 1078 ((MakeCallCommand) evt).getSessionDescription()); 1079 mState = SipSession.State.OUTGOING_CALL; 1080 startSessionTimer(((MakeCallCommand) evt).getTimeout()); 1081 return true; 1082 } 1083 return false; 1084 } 1085 1086 // timeout in seconds 1087 private void startSessionTimer(int timeout) { 1088 if (timeout > 0) { 1089 mTimer = new SessionTimer(); 1090 mTimer.start(timeout); 1091 } 1092 } 1093 1094 private void cancelSessionTimer() { 1095 if (mTimer != null) { 1096 mTimer.cancel(); 1097 mTimer = null; 1098 } 1099 } 1100 1101 private String createErrorMessage(Response response) { 1102 return String.format(SERVER_ERROR_PREFIX + "%s (%d)", 1103 response.getReasonPhrase(), response.getStatusCode()); 1104 } 1105 1106 private void establishCall() { 1107 mState = SipSession.State.IN_CALL; 1108 mInCall = true; 1109 cancelSessionTimer(); 1110 mProxy.onCallEstablished(this, mPeerSessionDescription); 1111 } 1112 1113 private void fallbackToPreviousInCall(int errorCode, String message) { 1114 mState = SipSession.State.IN_CALL; 1115 mProxy.onCallChangeFailed(this, errorCode, message); 1116 } 1117 1118 private void endCallNormally() { 1119 reset(); 1120 mProxy.onCallEnded(this); 1121 } 1122 1123 private void endCallOnError(int errorCode, String message) { 1124 reset(); 1125 mProxy.onError(this, errorCode, message); 1126 } 1127 1128 private void endCallOnBusy() { 1129 reset(); 1130 mProxy.onCallBusy(this); 1131 } 1132 1133 private void onError(int errorCode, String message) { 1134 cancelSessionTimer(); 1135 switch (mState) { 1136 case SipSession.State.REGISTERING: 1137 case SipSession.State.DEREGISTERING: 1138 onRegistrationFailed(errorCode, message); 1139 break; 1140 default: 1141 if ((errorCode != SipErrorCode.DATA_CONNECTION_LOST) 1142 && mInCall) { 1143 fallbackToPreviousInCall(errorCode, message); 1144 } else { 1145 endCallOnError(errorCode, message); 1146 } 1147 } 1148 } 1149 1150 1151 private void onError(Throwable exception) { 1152 exception = getRootCause(exception); 1153 onError(getErrorCode(exception), exception.toString()); 1154 } 1155 1156 private void onError(Response response) { 1157 int statusCode = response.getStatusCode(); 1158 if (!mInCall && (statusCode == Response.BUSY_HERE)) { 1159 endCallOnBusy(); 1160 } else { 1161 onError(getErrorCode(statusCode), createErrorMessage(response)); 1162 } 1163 } 1164 1165 private int getErrorCode(int responseStatusCode) { 1166 switch (responseStatusCode) { 1167 case Response.TEMPORARILY_UNAVAILABLE: 1168 case Response.FORBIDDEN: 1169 case Response.GONE: 1170 case Response.NOT_FOUND: 1171 case Response.NOT_ACCEPTABLE: 1172 case Response.NOT_ACCEPTABLE_HERE: 1173 return SipErrorCode.PEER_NOT_REACHABLE; 1174 1175 case Response.REQUEST_URI_TOO_LONG: 1176 case Response.ADDRESS_INCOMPLETE: 1177 case Response.AMBIGUOUS: 1178 return SipErrorCode.INVALID_REMOTE_URI; 1179 1180 case Response.REQUEST_TIMEOUT: 1181 return SipErrorCode.TIME_OUT; 1182 1183 default: 1184 if (responseStatusCode < 500) { 1185 return SipErrorCode.CLIENT_ERROR; 1186 } else { 1187 return SipErrorCode.SERVER_ERROR; 1188 } 1189 } 1190 } 1191 1192 private Throwable getRootCause(Throwable exception) { 1193 Throwable cause = exception.getCause(); 1194 while (cause != null) { 1195 exception = cause; 1196 cause = exception.getCause(); 1197 } 1198 return exception; 1199 } 1200 1201 private int getErrorCode(Throwable exception) { 1202 String message = exception.getMessage(); 1203 if (exception instanceof UnknownHostException) { 1204 return SipErrorCode.INVALID_REMOTE_URI; 1205 } else if (exception instanceof IOException) { 1206 return SipErrorCode.SOCKET_ERROR; 1207 } else if (message.startsWith(SERVER_ERROR_PREFIX)) { 1208 return SipErrorCode.SERVER_ERROR; 1209 } else { 1210 return SipErrorCode.CLIENT_ERROR; 1211 } 1212 } 1213 1214 private void onRegistrationDone(int duration) { 1215 reset(); 1216 mProxy.onRegistrationDone(this, duration); 1217 } 1218 1219 private void onRegistrationFailed(int errorCode, String message) { 1220 reset(); 1221 mProxy.onRegistrationFailed(this, errorCode, message); 1222 } 1223 1224 private void onRegistrationFailed(Throwable exception) { 1225 reset(); 1226 exception = getRootCause(exception); 1227 onRegistrationFailed(getErrorCode(exception), 1228 exception.toString()); 1229 } 1230 1231 private void onRegistrationFailed(Response response) { 1232 reset(); 1233 int statusCode = response.getStatusCode(); 1234 onRegistrationFailed(getErrorCode(statusCode), 1235 createErrorMessage(response)); 1236 } 1237 } 1238 1239 /** 1240 * @return true if the event is a request event matching the specified 1241 * method; false otherwise 1242 */ 1243 private static boolean isRequestEvent(String method, EventObject event) { 1244 try { 1245 if (event instanceof RequestEvent) { 1246 RequestEvent requestEvent = (RequestEvent) event; 1247 return method.equals(requestEvent.getRequest().getMethod()); 1248 } 1249 } catch (Throwable e) { 1250 } 1251 return false; 1252 } 1253 1254 private static String getCseqMethod(Message message) { 1255 return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod(); 1256 } 1257 1258 /** 1259 * @return true if the event is a response event and the CSeqHeader method 1260 * match the given arguments; false otherwise 1261 */ 1262 private static boolean expectResponse( 1263 String expectedMethod, EventObject evt) { 1264 if (evt instanceof ResponseEvent) { 1265 ResponseEvent event = (ResponseEvent) evt; 1266 Response response = event.getResponse(); 1267 return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); 1268 } 1269 return false; 1270 } 1271 1272 /** 1273 * @return true if the event is a response event and the response code and 1274 * CSeqHeader method match the given arguments; false otherwise 1275 */ 1276 private static boolean expectResponse( 1277 int responseCode, String expectedMethod, EventObject evt) { 1278 if (evt instanceof ResponseEvent) { 1279 ResponseEvent event = (ResponseEvent) evt; 1280 Response response = event.getResponse(); 1281 if (response.getStatusCode() == responseCode) { 1282 return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); 1283 } 1284 } 1285 return false; 1286 } 1287 1288 private static SipProfile createPeerProfile(Request request) 1289 throws SipException { 1290 try { 1291 FromHeader fromHeader = 1292 (FromHeader) request.getHeader(FromHeader.NAME); 1293 Address address = fromHeader.getAddress(); 1294 SipURI uri = (SipURI) address.getURI(); 1295 String username = uri.getUser(); 1296 if (username == null) username = ANONYMOUS; 1297 return new SipProfile.Builder(username, uri.getHost()) 1298 .setPort(uri.getPort()) 1299 .setDisplayName(address.getDisplayName()) 1300 .build(); 1301 } catch (IllegalArgumentException e) { 1302 throw new SipException("createPeerProfile()", e); 1303 } catch (ParseException e) { 1304 throw new SipException("createPeerProfile()", e); 1305 } 1306 } 1307 1308 private static boolean isLoggable(SipSessionImpl s) { 1309 if (s != null) { 1310 switch (s.mState) { 1311 case SipSession.State.PINGING: 1312 return DEBUG_PING; 1313 } 1314 } 1315 return DEBUG; 1316 } 1317 1318 private static boolean isLoggable(SipSessionImpl s, EventObject evt) { 1319 if (!isLoggable(s)) return false; 1320 if (evt == null) return false; 1321 1322 if (evt instanceof OptionsCommand) { 1323 return DEBUG_PING; 1324 } else if (evt instanceof ResponseEvent) { 1325 Response response = ((ResponseEvent) evt).getResponse(); 1326 if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { 1327 return DEBUG_PING; 1328 } 1329 return DEBUG; 1330 } else if (evt instanceof RequestEvent) { 1331 return DEBUG; 1332 } 1333 return false; 1334 } 1335 1336 private static String log(EventObject evt) { 1337 if (evt instanceof RequestEvent) { 1338 return ((RequestEvent) evt).getRequest().toString(); 1339 } else if (evt instanceof ResponseEvent) { 1340 return ((ResponseEvent) evt).getResponse().toString(); 1341 } else { 1342 return evt.toString(); 1343 } 1344 } 1345 1346 private class OptionsCommand extends EventObject { 1347 public OptionsCommand() { 1348 super(SipSessionGroup.this); 1349 } 1350 } 1351 1352 private class RegisterCommand extends EventObject { 1353 private int mDuration; 1354 1355 public RegisterCommand(int duration) { 1356 super(SipSessionGroup.this); 1357 mDuration = duration; 1358 } 1359 1360 public int getDuration() { 1361 return mDuration; 1362 } 1363 } 1364 1365 private class MakeCallCommand extends EventObject { 1366 private String mSessionDescription; 1367 private int mTimeout; // in seconds 1368 1369 public MakeCallCommand(SipProfile peerProfile, 1370 String sessionDescription) { 1371 this(peerProfile, sessionDescription, -1); 1372 } 1373 1374 public MakeCallCommand(SipProfile peerProfile, 1375 String sessionDescription, int timeout) { 1376 super(peerProfile); 1377 mSessionDescription = sessionDescription; 1378 mTimeout = timeout; 1379 } 1380 1381 public SipProfile getPeerProfile() { 1382 return (SipProfile) getSource(); 1383 } 1384 1385 public String getSessionDescription() { 1386 return mSessionDescription; 1387 } 1388 1389 public int getTimeout() { 1390 return mTimeout; 1391 } 1392 } 1393} 1394