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 17 package com.android.server.sip; 18 19 import gov.nist.javax.sip.clientauthutils.AccountManager; 20 import gov.nist.javax.sip.clientauthutils.UserCredentials; 21 import gov.nist.javax.sip.header.ProxyAuthenticate; 22 import gov.nist.javax.sip.header.ReferTo; 23 import gov.nist.javax.sip.header.SIPHeaderNames; 24 import gov.nist.javax.sip.header.StatusLine; 25 import gov.nist.javax.sip.header.WWWAuthenticate; 26 import gov.nist.javax.sip.header.extensions.ReferredByHeader; 27 import gov.nist.javax.sip.header.extensions.ReplacesHeader; 28 import gov.nist.javax.sip.message.SIPMessage; 29 import gov.nist.javax.sip.message.SIPResponse; 30 31 import android.net.sip.ISipSession; 32 import android.net.sip.ISipSessionListener; 33 import android.net.sip.SipErrorCode; 34 import android.net.sip.SipProfile; 35 import android.net.sip.SipSession; 36 import android.net.sip.SipSessionAdapter; 37 import android.text.TextUtils; 38 import android.util.Log; 39 40 import java.io.IOException; 41 import java.io.UnsupportedEncodingException; 42 import java.net.DatagramSocket; 43 import java.net.UnknownHostException; 44 import java.text.ParseException; 45 import java.util.Collection; 46 import java.util.EventObject; 47 import java.util.HashMap; 48 import java.util.Map; 49 import java.util.Properties; 50 import java.util.TooManyListenersException; 51 52 import javax.sip.ClientTransaction; 53 import javax.sip.Dialog; 54 import javax.sip.DialogTerminatedEvent; 55 import javax.sip.IOExceptionEvent; 56 import javax.sip.InvalidArgumentException; 57 import javax.sip.ListeningPoint; 58 import javax.sip.ObjectInUseException; 59 import javax.sip.RequestEvent; 60 import javax.sip.ResponseEvent; 61 import javax.sip.ServerTransaction; 62 import javax.sip.SipException; 63 import javax.sip.SipFactory; 64 import javax.sip.SipListener; 65 import javax.sip.SipProvider; 66 import javax.sip.SipStack; 67 import javax.sip.TimeoutEvent; 68 import javax.sip.Transaction; 69 import javax.sip.TransactionState; 70 import javax.sip.TransactionTerminatedEvent; 71 import javax.sip.TransactionUnavailableException; 72 import javax.sip.address.Address; 73 import javax.sip.address.SipURI; 74 import javax.sip.header.CSeqHeader; 75 import javax.sip.header.ContactHeader; 76 import javax.sip.header.ExpiresHeader; 77 import javax.sip.header.FromHeader; 78 import javax.sip.header.HeaderAddress; 79 import javax.sip.header.MinExpiresHeader; 80 import javax.sip.header.ReferToHeader; 81 import javax.sip.header.ViaHeader; 82 import javax.sip.message.Message; 83 import javax.sip.message.Request; 84 import javax.sip.message.Response; 85 86 87 /** 88 * Manages {@link ISipSession}'s for a SIP account. 89 */ 90 class SipSessionGroup implements SipListener { 91 private static final String TAG = "SipSession"; 92 private static final boolean DEBUG = true; 93 private static final boolean DEBUG_PING = DEBUG && false; 94 private static final String ANONYMOUS = "anonymous"; 95 // Limit the size of thread pool to 1 for the order issue when the phone is 96 // waken up from sleep and there are many packets to be processed in the SIP 97 // stack. Note: The default thread pool size in NIST SIP stack is -1 which is 98 // unlimited. 99 private static final String THREAD_POOL_SIZE = "1"; 100 private static final int EXPIRY_TIME = 3600; // in seconds 101 private static final int CANCEL_CALL_TIMER = 3; // in seconds 102 private static final int END_CALL_TIMER = 3; // in seconds 103 private static final int KEEPALIVE_TIMEOUT = 5; // in seconds 104 private static final int INCALL_KEEPALIVE_INTERVAL = 10; // in seconds 105 private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds 106 107 private static final EventObject DEREGISTER = new EventObject("Deregister"); 108 private static final EventObject END_CALL = new EventObject("End call"); 109 private static final EventObject HOLD_CALL = new EventObject("Hold call"); 110 private static final EventObject CONTINUE_CALL 111 = new EventObject("Continue call"); 112 113 private final SipProfile mLocalProfile; 114 private final String mPassword; 115 116 private SipStack mSipStack; 117 private SipHelper mSipHelper; 118 119 // session that processes INVITE requests 120 private SipSessionImpl mCallReceiverSession; 121 private String mLocalIp; 122 123 private SipWakeupTimer mWakeupTimer; 124 private SipWakeLock mWakeLock; 125 126 // call-id-to-SipSession map 127 private Map<String, SipSessionImpl> mSessionMap = 128 new HashMap<String, SipSessionImpl>(); 129 130 // external address observed from any response 131 private String mExternalIp; 132 private int mExternalPort; 133 134 /** 135 * @param myself the local profile with password crossed out 136 * @param password the password of the profile 137 * @throws IOException if cannot assign requested address 138 */ 139 public SipSessionGroup(String localIp, SipProfile myself, String password, 140 SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException, 141 IOException { 142 mLocalProfile = myself; 143 mPassword = password; 144 mWakeupTimer = timer; 145 mWakeLock = wakeLock; 146 reset(localIp); 147 } 148 149 // TODO: remove this method once SipWakeupTimer can better handle variety 150 // of timeout values 151 void setWakeupTimer(SipWakeupTimer timer) { 152 mWakeupTimer = timer; 153 } 154 155 synchronized void reset(String localIp) throws SipException, IOException { 156 mLocalIp = localIp; 157 if (localIp == null) return; 158 159 SipProfile myself = mLocalProfile; 160 SipFactory sipFactory = SipFactory.getInstance(); 161 Properties properties = new Properties(); 162 properties.setProperty("javax.sip.STACK_NAME", getStackName()); 163 properties.setProperty( 164 "gov.nist.javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE); 165 String outboundProxy = myself.getProxyAddress(); 166 if (!TextUtils.isEmpty(outboundProxy)) { 167 Log.v(TAG, "outboundProxy is " + outboundProxy); 168 properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy 169 + ":" + myself.getPort() + "/" + myself.getProtocol()); 170 } 171 SipStack stack = mSipStack = sipFactory.createSipStack(properties); 172 173 try { 174 SipProvider provider = stack.createSipProvider( 175 stack.createListeningPoint(localIp, allocateLocalPort(), 176 myself.getProtocol())); 177 provider.addSipListener(this); 178 mSipHelper = new SipHelper(stack, provider); 179 } catch (InvalidArgumentException e) { 180 throw new IOException(e.getMessage()); 181 } catch (TooManyListenersException e) { 182 // must never happen 183 throw new SipException("SipSessionGroup constructor", e); 184 } 185 Log.d(TAG, " start stack for " + myself.getUriString()); 186 stack.start(); 187 188 mCallReceiverSession = null; 189 mSessionMap.clear(); 190 191 resetExternalAddress(); 192 } 193 194 synchronized void onConnectivityChanged() { 195 SipSessionImpl[] ss = mSessionMap.values().toArray( 196 new SipSessionImpl[mSessionMap.size()]); 197 // Iterate on the copied array instead of directly on mSessionMap to 198 // avoid ConcurrentModificationException being thrown when 199 // SipSessionImpl removes itself from mSessionMap in onError() in the 200 // following loop. 201 for (SipSessionImpl s : ss) { 202 s.onError(SipErrorCode.DATA_CONNECTION_LOST, 203 "data connection lost"); 204 } 205 } 206 207 synchronized void resetExternalAddress() { 208 Log.d(TAG, " reset external addr on " + mSipStack); 209 mExternalIp = null; 210 mExternalPort = 0; 211 } 212 213 public SipProfile getLocalProfile() { 214 return mLocalProfile; 215 } 216 217 public String getLocalProfileUri() { 218 return mLocalProfile.getUriString(); 219 } 220 221 private String getStackName() { 222 return "stack" + System.currentTimeMillis(); 223 } 224 225 public synchronized void close() { 226 Log.d(TAG, " close stack for " + mLocalProfile.getUriString()); 227 onConnectivityChanged(); 228 mSessionMap.clear(); 229 closeToNotReceiveCalls(); 230 if (mSipStack != null) { 231 mSipStack.stop(); 232 mSipStack = null; 233 mSipHelper = null; 234 } 235 } 236 237 public synchronized boolean isClosed() { 238 return (mSipStack == null); 239 } 240 241 // For internal use, require listener not to block in callbacks. 242 public synchronized void openToReceiveCalls(ISipSessionListener listener) { 243 if (mCallReceiverSession == null) { 244 mCallReceiverSession = new SipSessionCallReceiverImpl(listener); 245 } else { 246 mCallReceiverSession.setListener(listener); 247 } 248 } 249 250 public synchronized void closeToNotReceiveCalls() { 251 mCallReceiverSession = null; 252 } 253 254 public ISipSession createSession(ISipSessionListener listener) { 255 return (isClosed() ? null : new SipSessionImpl(listener)); 256 } 257 258 private static int allocateLocalPort() throws SipException { 259 try { 260 DatagramSocket s = new DatagramSocket(); 261 int localPort = s.getLocalPort(); 262 s.close(); 263 return localPort; 264 } catch (IOException e) { 265 throw new SipException("allocateLocalPort()", e); 266 } 267 } 268 269 synchronized boolean containsSession(String callId) { 270 return mSessionMap.containsKey(callId); 271 } 272 273 private synchronized SipSessionImpl getSipSession(EventObject event) { 274 String key = SipHelper.getCallId(event); 275 SipSessionImpl session = mSessionMap.get(key); 276 if ((session != null) && isLoggable(session)) { 277 Log.d(TAG, "session key from event: " + key); 278 Log.d(TAG, "active sessions:"); 279 for (String k : mSessionMap.keySet()) { 280 Log.d(TAG, " ..." + k + ": " + mSessionMap.get(k)); 281 } 282 } 283 return ((session != null) ? session : mCallReceiverSession); 284 } 285 286 private synchronized void addSipSession(SipSessionImpl newSession) { 287 removeSipSession(newSession); 288 String key = newSession.getCallId(); 289 mSessionMap.put(key, newSession); 290 if (isLoggable(newSession)) { 291 Log.d(TAG, "+++ add a session with key: '" + key + "'"); 292 for (String k : mSessionMap.keySet()) { 293 Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); 294 } 295 } 296 } 297 298 private synchronized void removeSipSession(SipSessionImpl session) { 299 if (session == mCallReceiverSession) return; 300 String key = session.getCallId(); 301 SipSessionImpl s = mSessionMap.remove(key); 302 // sanity check 303 if ((s != null) && (s != session)) { 304 Log.w(TAG, "session " + session + " is not associated with key '" 305 + key + "'"); 306 mSessionMap.put(key, s); 307 for (Map.Entry<String, SipSessionImpl> entry 308 : mSessionMap.entrySet()) { 309 if (entry.getValue() == s) { 310 key = entry.getKey(); 311 mSessionMap.remove(key); 312 } 313 } 314 } 315 316 if ((s != null) && isLoggable(s)) { 317 Log.d(TAG, "remove session " + session + " @key '" + key + "'"); 318 for (String k : mSessionMap.keySet()) { 319 Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); 320 } 321 } 322 } 323 324 public void processRequest(final RequestEvent event) { 325 if (isRequestEvent(Request.INVITE, event)) { 326 if (DEBUG) Log.d(TAG, "<<<<< got INVITE, thread:" 327 + Thread.currentThread()); 328 // Acquire a wake lock and keep it for WAKE_LOCK_HOLDING_TIME; 329 // should be large enough to bring up the app. 330 mWakeLock.acquire(WAKE_LOCK_HOLDING_TIME); 331 } 332 process(event); 333 } 334 335 public void processResponse(ResponseEvent event) { 336 process(event); 337 } 338 339 public void processIOException(IOExceptionEvent event) { 340 process(event); 341 } 342 343 public void processTimeout(TimeoutEvent event) { 344 process(event); 345 } 346 347 public void processTransactionTerminated(TransactionTerminatedEvent event) { 348 process(event); 349 } 350 351 public void processDialogTerminated(DialogTerminatedEvent event) { 352 process(event); 353 } 354 355 private synchronized void process(EventObject event) { 356 SipSessionImpl session = getSipSession(event); 357 try { 358 boolean isLoggable = isLoggable(session, event); 359 boolean processed = (session != null) && session.process(event); 360 if (isLoggable && processed) { 361 Log.d(TAG, "new state after: " 362 + SipSession.State.toString(session.mState)); 363 } 364 } catch (Throwable e) { 365 Log.w(TAG, "event process error: " + event, e); 366 session.onError(e); 367 } 368 } 369 370 private String extractContent(Message message) { 371 // Currently we do not support secure MIME bodies. 372 byte[] bytes = message.getRawContent(); 373 if (bytes != null) { 374 try { 375 if (message instanceof SIPMessage) { 376 return ((SIPMessage) message).getMessageContent(); 377 } else { 378 return new String(bytes, "UTF-8"); 379 } 380 } catch (UnsupportedEncodingException e) { 381 } 382 } 383 return null; 384 } 385 386 private void extractExternalAddress(ResponseEvent evt) { 387 Response response = evt.getResponse(); 388 ViaHeader viaHeader = (ViaHeader)(response.getHeader( 389 SIPHeaderNames.VIA)); 390 if (viaHeader == null) return; 391 int rport = viaHeader.getRPort(); 392 String externalIp = viaHeader.getReceived(); 393 if ((rport > 0) && (externalIp != null)) { 394 mExternalIp = externalIp; 395 mExternalPort = rport; 396 Log.d(TAG, " got external addr " + externalIp + ":" + rport 397 + " on " + mSipStack); 398 } 399 } 400 401 private SipSessionImpl createNewSession(RequestEvent event, 402 ISipSessionListener listener, ServerTransaction transaction, 403 int newState) throws SipException { 404 SipSessionImpl newSession = new SipSessionImpl(listener); 405 newSession.mServerTransaction = transaction; 406 newSession.mState = newState; 407 newSession.mDialog = newSession.mServerTransaction.getDialog(); 408 newSession.mInviteReceived = event; 409 newSession.mPeerProfile = createPeerProfile((HeaderAddress) 410 event.getRequest().getHeader(FromHeader.NAME)); 411 newSession.mPeerSessionDescription = 412 extractContent(event.getRequest()); 413 return newSession; 414 } 415 416 private class SipSessionCallReceiverImpl extends SipSessionImpl { 417 public SipSessionCallReceiverImpl(ISipSessionListener listener) { 418 super(listener); 419 } 420 421 private int processInviteWithReplaces(RequestEvent event, 422 ReplacesHeader replaces) { 423 String callId = replaces.getCallId(); 424 SipSessionImpl session = mSessionMap.get(callId); 425 if (session == null) { 426 return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; 427 } 428 429 Dialog dialog = session.mDialog; 430 if (dialog == null) return Response.DECLINE; 431 432 if (!dialog.getLocalTag().equals(replaces.getToTag()) || 433 !dialog.getRemoteTag().equals(replaces.getFromTag())) { 434 // No match is found, returns 481. 435 return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; 436 } 437 438 ReferredByHeader referredBy = (ReferredByHeader) event.getRequest() 439 .getHeader(ReferredByHeader.NAME); 440 if ((referredBy == null) || 441 !dialog.getRemoteParty().equals(referredBy.getAddress())) { 442 return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; 443 } 444 return Response.OK; 445 } 446 447 private void processNewInviteRequest(RequestEvent event) 448 throws SipException { 449 ReplacesHeader replaces = (ReplacesHeader) event.getRequest() 450 .getHeader(ReplacesHeader.NAME); 451 SipSessionImpl newSession = null; 452 if (replaces != null) { 453 int response = processInviteWithReplaces(event, replaces); 454 if (DEBUG) { 455 Log.v(TAG, "ReplacesHeader: " + replaces 456 + " response=" + response); 457 } 458 if (response == Response.OK) { 459 SipSessionImpl replacedSession = 460 mSessionMap.get(replaces.getCallId()); 461 // got INVITE w/ replaces request. 462 newSession = createNewSession(event, 463 replacedSession.mProxy.getListener(), 464 mSipHelper.getServerTransaction(event), 465 SipSession.State.INCOMING_CALL); 466 newSession.mProxy.onCallTransferring(newSession, 467 newSession.mPeerSessionDescription); 468 } else { 469 mSipHelper.sendResponse(event, response); 470 } 471 } else { 472 // New Incoming call. 473 newSession = createNewSession(event, mProxy, 474 mSipHelper.sendRinging(event, generateTag()), 475 SipSession.State.INCOMING_CALL); 476 mProxy.onRinging(newSession, newSession.mPeerProfile, 477 newSession.mPeerSessionDescription); 478 } 479 if (newSession != null) addSipSession(newSession); 480 } 481 482 public boolean process(EventObject evt) throws SipException { 483 if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " 484 + SipSession.State.toString(mState) + ": processing " 485 + log(evt)); 486 if (isRequestEvent(Request.INVITE, evt)) { 487 processNewInviteRequest((RequestEvent) evt); 488 return true; 489 } else if (isRequestEvent(Request.OPTIONS, evt)) { 490 mSipHelper.sendResponse((RequestEvent) evt, Response.OK); 491 return true; 492 } else { 493 return false; 494 } 495 } 496 } 497 498 static interface KeepAliveProcessCallback { 499 /** Invoked when the response of keeping alive comes back. */ 500 void onResponse(boolean portChanged); 501 void onError(int errorCode, String description); 502 } 503 504 class SipSessionImpl extends ISipSession.Stub { 505 SipProfile mPeerProfile; 506 SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); 507 int mState = SipSession.State.READY_TO_CALL; 508 RequestEvent mInviteReceived; 509 Dialog mDialog; 510 ServerTransaction mServerTransaction; 511 ClientTransaction mClientTransaction; 512 String mPeerSessionDescription; 513 boolean mInCall; 514 SessionTimer mSessionTimer; 515 int mAuthenticationRetryCount; 516 517 private KeepAliveProcess mKeepAliveProcess; 518 519 private SipSessionImpl mKeepAliveSession; 520 521 // the following three members are used for handling refer request. 522 SipSessionImpl mReferSession; 523 ReferredByHeader mReferredBy; 524 String mReplaces; 525 526 // lightweight timer 527 class SessionTimer { 528 private boolean mRunning = true; 529 530 void start(final int timeout) { 531 new Thread(new Runnable() { 532 public void run() { 533 sleep(timeout); 534 if (mRunning) timeout(); 535 } 536 }, "SipSessionTimerThread").start(); 537 } 538 539 synchronized void cancel() { 540 mRunning = false; 541 this.notify(); 542 } 543 544 private void timeout() { 545 synchronized (SipSessionGroup.this) { 546 onError(SipErrorCode.TIME_OUT, "Session timed out!"); 547 } 548 } 549 550 private synchronized void sleep(int timeout) { 551 try { 552 this.wait(timeout * 1000); 553 } catch (InterruptedException e) { 554 Log.e(TAG, "session timer interrupted!"); 555 } 556 } 557 } 558 559 public SipSessionImpl(ISipSessionListener listener) { 560 setListener(listener); 561 } 562 563 SipSessionImpl duplicate() { 564 return new SipSessionImpl(mProxy.getListener()); 565 } 566 567 private void reset() { 568 mInCall = false; 569 removeSipSession(this); 570 mPeerProfile = null; 571 mState = SipSession.State.READY_TO_CALL; 572 mInviteReceived = null; 573 mPeerSessionDescription = null; 574 mAuthenticationRetryCount = 0; 575 mReferSession = null; 576 mReferredBy = null; 577 mReplaces = null; 578 579 if (mDialog != null) mDialog.delete(); 580 mDialog = null; 581 582 try { 583 if (mServerTransaction != null) mServerTransaction.terminate(); 584 } catch (ObjectInUseException e) { 585 // ignored 586 } 587 mServerTransaction = null; 588 589 try { 590 if (mClientTransaction != null) mClientTransaction.terminate(); 591 } catch (ObjectInUseException e) { 592 // ignored 593 } 594 mClientTransaction = null; 595 596 cancelSessionTimer(); 597 598 if (mKeepAliveSession != null) { 599 mKeepAliveSession.stopKeepAliveProcess(); 600 mKeepAliveSession = null; 601 } 602 } 603 604 public boolean isInCall() { 605 return mInCall; 606 } 607 608 public String getLocalIp() { 609 return mLocalIp; 610 } 611 612 public SipProfile getLocalProfile() { 613 return mLocalProfile; 614 } 615 616 public SipProfile getPeerProfile() { 617 return mPeerProfile; 618 } 619 620 public String getCallId() { 621 return SipHelper.getCallId(getTransaction()); 622 } 623 624 private Transaction getTransaction() { 625 if (mClientTransaction != null) return mClientTransaction; 626 if (mServerTransaction != null) return mServerTransaction; 627 return null; 628 } 629 630 public int getState() { 631 return mState; 632 } 633 634 public void setListener(ISipSessionListener listener) { 635 mProxy.setListener((listener instanceof SipSessionListenerProxy) 636 ? ((SipSessionListenerProxy) listener).getListener() 637 : listener); 638 } 639 640 // process the command in a new thread 641 private void doCommandAsync(final EventObject command) { 642 new Thread(new Runnable() { 643 public void run() { 644 try { 645 processCommand(command); 646 } catch (Throwable e) { 647 Log.w(TAG, "command error: " + command + ": " 648 + mLocalProfile.getUriString(), 649 getRootCause(e)); 650 onError(e); 651 } 652 } 653 }, "SipSessionAsyncCmdThread").start(); 654 } 655 656 public void makeCall(SipProfile peerProfile, String sessionDescription, 657 int timeout) { 658 doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription, 659 timeout)); 660 } 661 662 public void answerCall(String sessionDescription, int timeout) { 663 synchronized (SipSessionGroup.this) { 664 if (mPeerProfile == null) return; 665 doCommandAsync(new MakeCallCommand(mPeerProfile, 666 sessionDescription, timeout)); 667 } 668 } 669 670 public void endCall() { 671 doCommandAsync(END_CALL); 672 } 673 674 public void changeCall(String sessionDescription, int timeout) { 675 synchronized (SipSessionGroup.this) { 676 if (mPeerProfile == null) return; 677 doCommandAsync(new MakeCallCommand(mPeerProfile, 678 sessionDescription, timeout)); 679 } 680 } 681 682 public void register(int duration) { 683 doCommandAsync(new RegisterCommand(duration)); 684 } 685 686 public void unregister() { 687 doCommandAsync(DEREGISTER); 688 } 689 690 private void processCommand(EventObject command) throws SipException { 691 if (isLoggable(command)) Log.d(TAG, "process cmd: " + command); 692 if (!process(command)) { 693 onError(SipErrorCode.IN_PROGRESS, 694 "cannot initiate a new transaction to execute: " 695 + command); 696 } 697 } 698 699 protected String generateTag() { 700 // 32-bit randomness 701 return String.valueOf((long) (Math.random() * 0x100000000L)); 702 } 703 704 public String toString() { 705 try { 706 String s = super.toString(); 707 return s.substring(s.indexOf("@")) + ":" 708 + SipSession.State.toString(mState); 709 } catch (Throwable e) { 710 return super.toString(); 711 } 712 } 713 714 public boolean process(EventObject evt) throws SipException { 715 if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " 716 + SipSession.State.toString(mState) + ": processing " 717 + log(evt)); 718 synchronized (SipSessionGroup.this) { 719 if (isClosed()) return false; 720 721 if (mKeepAliveProcess != null) { 722 // event consumed by keepalive process 723 if (mKeepAliveProcess.process(evt)) return true; 724 } 725 726 Dialog dialog = null; 727 if (evt instanceof RequestEvent) { 728 dialog = ((RequestEvent) evt).getDialog(); 729 } else if (evt instanceof ResponseEvent) { 730 dialog = ((ResponseEvent) evt).getDialog(); 731 extractExternalAddress((ResponseEvent) evt); 732 } 733 if (dialog != null) mDialog = dialog; 734 735 boolean processed; 736 737 switch (mState) { 738 case SipSession.State.REGISTERING: 739 case SipSession.State.DEREGISTERING: 740 processed = registeringToReady(evt); 741 break; 742 case SipSession.State.READY_TO_CALL: 743 processed = readyForCall(evt); 744 break; 745 case SipSession.State.INCOMING_CALL: 746 processed = incomingCall(evt); 747 break; 748 case SipSession.State.INCOMING_CALL_ANSWERING: 749 processed = incomingCallToInCall(evt); 750 break; 751 case SipSession.State.OUTGOING_CALL: 752 case SipSession.State.OUTGOING_CALL_RING_BACK: 753 processed = outgoingCall(evt); 754 break; 755 case SipSession.State.OUTGOING_CALL_CANCELING: 756 processed = outgoingCallToReady(evt); 757 break; 758 case SipSession.State.IN_CALL: 759 processed = inCall(evt); 760 break; 761 case SipSession.State.ENDING_CALL: 762 processed = endingCall(evt); 763 break; 764 default: 765 processed = false; 766 } 767 return (processed || processExceptions(evt)); 768 } 769 } 770 771 private boolean processExceptions(EventObject evt) throws SipException { 772 if (isRequestEvent(Request.BYE, evt)) { 773 // terminate the call whenever a BYE is received 774 mSipHelper.sendResponse((RequestEvent) evt, Response.OK); 775 endCallNormally(); 776 return true; 777 } else if (isRequestEvent(Request.CANCEL, evt)) { 778 mSipHelper.sendResponse((RequestEvent) evt, 779 Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); 780 return true; 781 } else if (evt instanceof TransactionTerminatedEvent) { 782 if (isCurrentTransaction((TransactionTerminatedEvent) evt)) { 783 if (evt instanceof TimeoutEvent) { 784 processTimeout((TimeoutEvent) evt); 785 } else { 786 processTransactionTerminated( 787 (TransactionTerminatedEvent) evt); 788 } 789 return true; 790 } 791 } else if (isRequestEvent(Request.OPTIONS, evt)) { 792 mSipHelper.sendResponse((RequestEvent) evt, Response.OK); 793 return true; 794 } else if (evt instanceof DialogTerminatedEvent) { 795 processDialogTerminated((DialogTerminatedEvent) evt); 796 return true; 797 } 798 return false; 799 } 800 801 private void processDialogTerminated(DialogTerminatedEvent event) { 802 if (mDialog == event.getDialog()) { 803 onError(new SipException("dialog terminated")); 804 } else { 805 Log.d(TAG, "not the current dialog; current=" + mDialog 806 + ", terminated=" + event.getDialog()); 807 } 808 } 809 810 private boolean isCurrentTransaction(TransactionTerminatedEvent event) { 811 Transaction current = event.isServerTransaction() 812 ? mServerTransaction 813 : mClientTransaction; 814 Transaction target = event.isServerTransaction() 815 ? event.getServerTransaction() 816 : event.getClientTransaction(); 817 818 if ((current != target) && (mState != SipSession.State.PINGING)) { 819 Log.d(TAG, "not the current transaction; current=" 820 + toString(current) + ", target=" + toString(target)); 821 return false; 822 } else if (current != null) { 823 Log.d(TAG, "transaction terminated: " + toString(current)); 824 return true; 825 } else { 826 // no transaction; shouldn't be here; ignored 827 return true; 828 } 829 } 830 831 private String toString(Transaction transaction) { 832 if (transaction == null) return "null"; 833 Request request = transaction.getRequest(); 834 Dialog dialog = transaction.getDialog(); 835 CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME); 836 return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(), 837 cseq.getSeqNumber(), transaction.getState(), 838 ((dialog == null) ? "-" : dialog.getState())); 839 } 840 841 private void processTransactionTerminated( 842 TransactionTerminatedEvent event) { 843 switch (mState) { 844 case SipSession.State.IN_CALL: 845 case SipSession.State.READY_TO_CALL: 846 Log.d(TAG, "Transaction terminated; do nothing"); 847 break; 848 default: 849 Log.d(TAG, "Transaction terminated early: " + this); 850 onError(SipErrorCode.TRANSACTION_TERMINTED, 851 "transaction terminated"); 852 } 853 } 854 855 private void processTimeout(TimeoutEvent event) { 856 Log.d(TAG, "processing Timeout..."); 857 switch (mState) { 858 case SipSession.State.REGISTERING: 859 case SipSession.State.DEREGISTERING: 860 reset(); 861 mProxy.onRegistrationTimeout(this); 862 break; 863 case SipSession.State.INCOMING_CALL: 864 case SipSession.State.INCOMING_CALL_ANSWERING: 865 case SipSession.State.OUTGOING_CALL: 866 case SipSession.State.OUTGOING_CALL_CANCELING: 867 onError(SipErrorCode.TIME_OUT, event.toString()); 868 break; 869 870 default: 871 Log.d(TAG, " do nothing"); 872 break; 873 } 874 } 875 876 private int getExpiryTime(Response response) { 877 int time = -1; 878 ContactHeader contact = (ContactHeader) response.getHeader(ContactHeader.NAME); 879 if (contact != null) { 880 time = contact.getExpires(); 881 } 882 ExpiresHeader expires = (ExpiresHeader) response.getHeader(ExpiresHeader.NAME); 883 if (expires != null && (time < 0 || time > expires.getExpires())) { 884 time = expires.getExpires(); 885 } 886 if (time <= 0) { 887 time = EXPIRY_TIME; 888 } 889 expires = (ExpiresHeader) response.getHeader(MinExpiresHeader.NAME); 890 if (expires != null && time < expires.getExpires()) { 891 time = expires.getExpires(); 892 } 893 Log.v(TAG, "Expiry time = " + time); 894 return time; 895 } 896 897 private boolean registeringToReady(EventObject evt) 898 throws SipException { 899 if (expectResponse(Request.REGISTER, evt)) { 900 ResponseEvent event = (ResponseEvent) evt; 901 Response response = event.getResponse(); 902 903 int statusCode = response.getStatusCode(); 904 switch (statusCode) { 905 case Response.OK: 906 int state = mState; 907 onRegistrationDone((state == SipSession.State.REGISTERING) 908 ? getExpiryTime(((ResponseEvent) evt).getResponse()) 909 : -1); 910 return true; 911 case Response.UNAUTHORIZED: 912 case Response.PROXY_AUTHENTICATION_REQUIRED: 913 handleAuthentication(event); 914 return true; 915 default: 916 if (statusCode >= 500) { 917 onRegistrationFailed(response); 918 return true; 919 } 920 } 921 } 922 return false; 923 } 924 925 private boolean handleAuthentication(ResponseEvent event) 926 throws SipException { 927 Response response = event.getResponse(); 928 String nonce = getNonceFromResponse(response); 929 if (nonce == null) { 930 onError(SipErrorCode.SERVER_ERROR, 931 "server does not provide challenge"); 932 return false; 933 } else if (mAuthenticationRetryCount < 2) { 934 mClientTransaction = mSipHelper.handleChallenge( 935 event, getAccountManager()); 936 mDialog = mClientTransaction.getDialog(); 937 mAuthenticationRetryCount++; 938 if (isLoggable(this, event)) { 939 Log.d(TAG, " authentication retry count=" 940 + mAuthenticationRetryCount); 941 } 942 return true; 943 } else { 944 if (crossDomainAuthenticationRequired(response)) { 945 onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, 946 getRealmFromResponse(response)); 947 } else { 948 onError(SipErrorCode.INVALID_CREDENTIALS, 949 "incorrect username or password"); 950 } 951 return false; 952 } 953 } 954 955 private boolean crossDomainAuthenticationRequired(Response response) { 956 String realm = getRealmFromResponse(response); 957 if (realm == null) realm = ""; 958 return !mLocalProfile.getSipDomain().trim().equals(realm.trim()); 959 } 960 961 private AccountManager getAccountManager() { 962 return new AccountManager() { 963 public UserCredentials getCredentials(ClientTransaction 964 challengedTransaction, String realm) { 965 return new UserCredentials() { 966 public String getUserName() { 967 String username = mLocalProfile.getAuthUserName(); 968 return (!TextUtils.isEmpty(username) ? username : 969 mLocalProfile.getUserName()); 970 } 971 972 public String getPassword() { 973 return mPassword; 974 } 975 976 public String getSipDomain() { 977 return mLocalProfile.getSipDomain(); 978 } 979 }; 980 } 981 }; 982 } 983 984 private String getRealmFromResponse(Response response) { 985 WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( 986 SIPHeaderNames.WWW_AUTHENTICATE); 987 if (wwwAuth != null) return wwwAuth.getRealm(); 988 ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( 989 SIPHeaderNames.PROXY_AUTHENTICATE); 990 return (proxyAuth == null) ? null : proxyAuth.getRealm(); 991 } 992 993 private String getNonceFromResponse(Response response) { 994 WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( 995 SIPHeaderNames.WWW_AUTHENTICATE); 996 if (wwwAuth != null) return wwwAuth.getNonce(); 997 ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( 998 SIPHeaderNames.PROXY_AUTHENTICATE); 999 return (proxyAuth == null) ? null : proxyAuth.getNonce(); 1000 } 1001 1002 private String getResponseString(int statusCode) { 1003 StatusLine statusLine = new StatusLine(); 1004 statusLine.setStatusCode(statusCode); 1005 statusLine.setReasonPhrase(SIPResponse.getReasonPhrase(statusCode)); 1006 return statusLine.encode(); 1007 } 1008 1009 private boolean readyForCall(EventObject evt) throws SipException { 1010 // expect MakeCallCommand, RegisterCommand, DEREGISTER 1011 if (evt instanceof MakeCallCommand) { 1012 mState = SipSession.State.OUTGOING_CALL; 1013 MakeCallCommand cmd = (MakeCallCommand) evt; 1014 mPeerProfile = cmd.getPeerProfile(); 1015 if (mReferSession != null) { 1016 mSipHelper.sendReferNotify(mReferSession.mDialog, 1017 getResponseString(Response.TRYING)); 1018 } 1019 mClientTransaction = mSipHelper.sendInvite( 1020 mLocalProfile, mPeerProfile, cmd.getSessionDescription(), 1021 generateTag(), mReferredBy, mReplaces); 1022 mDialog = mClientTransaction.getDialog(); 1023 addSipSession(this); 1024 startSessionTimer(cmd.getTimeout()); 1025 mProxy.onCalling(this); 1026 return true; 1027 } else if (evt instanceof RegisterCommand) { 1028 mState = SipSession.State.REGISTERING; 1029 int duration = ((RegisterCommand) evt).getDuration(); 1030 mClientTransaction = mSipHelper.sendRegister(mLocalProfile, 1031 generateTag(), duration); 1032 mDialog = mClientTransaction.getDialog(); 1033 addSipSession(this); 1034 mProxy.onRegistering(this); 1035 return true; 1036 } else if (DEREGISTER == evt) { 1037 mState = SipSession.State.DEREGISTERING; 1038 mClientTransaction = mSipHelper.sendRegister(mLocalProfile, 1039 generateTag(), 0); 1040 mDialog = mClientTransaction.getDialog(); 1041 addSipSession(this); 1042 mProxy.onRegistering(this); 1043 return true; 1044 } 1045 return false; 1046 } 1047 1048 private boolean incomingCall(EventObject evt) throws SipException { 1049 // expect MakeCallCommand(answering) , END_CALL cmd , Cancel 1050 if (evt instanceof MakeCallCommand) { 1051 // answer call 1052 mState = SipSession.State.INCOMING_CALL_ANSWERING; 1053 mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, 1054 mLocalProfile, 1055 ((MakeCallCommand) evt).getSessionDescription(), 1056 mServerTransaction, 1057 mExternalIp, mExternalPort); 1058 startSessionTimer(((MakeCallCommand) evt).getTimeout()); 1059 return true; 1060 } else if (END_CALL == evt) { 1061 mSipHelper.sendInviteBusyHere(mInviteReceived, 1062 mServerTransaction); 1063 endCallNormally(); 1064 return true; 1065 } else if (isRequestEvent(Request.CANCEL, evt)) { 1066 RequestEvent event = (RequestEvent) evt; 1067 mSipHelper.sendResponse(event, Response.OK); 1068 mSipHelper.sendInviteRequestTerminated( 1069 mInviteReceived.getRequest(), mServerTransaction); 1070 endCallNormally(); 1071 return true; 1072 } 1073 return false; 1074 } 1075 1076 private boolean incomingCallToInCall(EventObject evt) 1077 throws SipException { 1078 // expect ACK, CANCEL request 1079 if (isRequestEvent(Request.ACK, evt)) { 1080 String sdp = extractContent(((RequestEvent) evt).getRequest()); 1081 if (sdp != null) mPeerSessionDescription = sdp; 1082 if (mPeerSessionDescription == null) { 1083 onError(SipErrorCode.CLIENT_ERROR, "peer sdp is empty"); 1084 } else { 1085 establishCall(false); 1086 } 1087 return true; 1088 } else if (isRequestEvent(Request.CANCEL, evt)) { 1089 // http://tools.ietf.org/html/rfc3261#section-9.2 1090 // Final response has been sent; do nothing here. 1091 return true; 1092 } 1093 return false; 1094 } 1095 1096 private boolean outgoingCall(EventObject evt) throws SipException { 1097 if (expectResponse(Request.INVITE, evt)) { 1098 ResponseEvent event = (ResponseEvent) evt; 1099 Response response = event.getResponse(); 1100 1101 int statusCode = response.getStatusCode(); 1102 switch (statusCode) { 1103 case Response.RINGING: 1104 case Response.CALL_IS_BEING_FORWARDED: 1105 case Response.QUEUED: 1106 case Response.SESSION_PROGRESS: 1107 // feedback any provisional responses (except TRYING) as 1108 // ring back for better UX 1109 if (mState == SipSession.State.OUTGOING_CALL) { 1110 mState = SipSession.State.OUTGOING_CALL_RING_BACK; 1111 cancelSessionTimer(); 1112 mProxy.onRingingBack(this); 1113 } 1114 return true; 1115 case Response.OK: 1116 if (mReferSession != null) { 1117 mSipHelper.sendReferNotify(mReferSession.mDialog, 1118 getResponseString(Response.OK)); 1119 // since we don't need to remember the session anymore. 1120 mReferSession = null; 1121 } 1122 mSipHelper.sendInviteAck(event, mDialog); 1123 mPeerSessionDescription = extractContent(response); 1124 establishCall(true); 1125 return true; 1126 case Response.UNAUTHORIZED: 1127 case Response.PROXY_AUTHENTICATION_REQUIRED: 1128 if (handleAuthentication(event)) { 1129 addSipSession(this); 1130 } 1131 return true; 1132 case Response.REQUEST_PENDING: 1133 // TODO: 1134 // rfc3261#section-14.1; re-schedule invite 1135 return true; 1136 default: 1137 if (mReferSession != null) { 1138 mSipHelper.sendReferNotify(mReferSession.mDialog, 1139 getResponseString(Response.SERVICE_UNAVAILABLE)); 1140 } 1141 if (statusCode >= 400) { 1142 // error: an ack is sent automatically by the stack 1143 onError(response); 1144 return true; 1145 } else if (statusCode >= 300) { 1146 // TODO: handle 3xx (redirect) 1147 } else { 1148 return true; 1149 } 1150 } 1151 return false; 1152 } else if (END_CALL == evt) { 1153 // RFC says that UA should not send out cancel when no 1154 // response comes back yet. We are cheating for not checking 1155 // response. 1156 mState = SipSession.State.OUTGOING_CALL_CANCELING; 1157 mSipHelper.sendCancel(mClientTransaction); 1158 startSessionTimer(CANCEL_CALL_TIMER); 1159 return true; 1160 } else if (isRequestEvent(Request.INVITE, evt)) { 1161 // Call self? Send BUSY HERE so server may redirect the call to 1162 // voice mailbox. 1163 RequestEvent event = (RequestEvent) evt; 1164 mSipHelper.sendInviteBusyHere(event, 1165 event.getServerTransaction()); 1166 return true; 1167 } 1168 return false; 1169 } 1170 1171 private boolean outgoingCallToReady(EventObject evt) 1172 throws SipException { 1173 if (evt instanceof ResponseEvent) { 1174 ResponseEvent event = (ResponseEvent) evt; 1175 Response response = event.getResponse(); 1176 int statusCode = response.getStatusCode(); 1177 if (expectResponse(Request.CANCEL, evt)) { 1178 if (statusCode == Response.OK) { 1179 // do nothing; wait for REQUEST_TERMINATED 1180 return true; 1181 } 1182 } else if (expectResponse(Request.INVITE, evt)) { 1183 switch (statusCode) { 1184 case Response.OK: 1185 outgoingCall(evt); // abort Cancel 1186 return true; 1187 case Response.REQUEST_TERMINATED: 1188 endCallNormally(); 1189 return true; 1190 } 1191 } else { 1192 return false; 1193 } 1194 1195 if (statusCode >= 400) { 1196 onError(response); 1197 return true; 1198 } 1199 } else if (evt instanceof TransactionTerminatedEvent) { 1200 // rfc3261#section-14.1: 1201 // if re-invite gets timed out, terminate the dialog; but 1202 // re-invite is not reliable, just let it go and pretend 1203 // nothing happened. 1204 onError(new SipException("timed out")); 1205 } 1206 return false; 1207 } 1208 1209 private boolean processReferRequest(RequestEvent event) 1210 throws SipException { 1211 try { 1212 ReferToHeader referto = (ReferToHeader) event.getRequest() 1213 .getHeader(ReferTo.NAME); 1214 Address address = referto.getAddress(); 1215 SipURI uri = (SipURI) address.getURI(); 1216 String replacesHeader = uri.getHeader(ReplacesHeader.NAME); 1217 String username = uri.getUser(); 1218 if (username == null) { 1219 mSipHelper.sendResponse(event, Response.BAD_REQUEST); 1220 return false; 1221 } 1222 // send notify accepted 1223 mSipHelper.sendResponse(event, Response.ACCEPTED); 1224 SipSessionImpl newSession = createNewSession(event, 1225 this.mProxy.getListener(), 1226 mSipHelper.getServerTransaction(event), 1227 SipSession.State.READY_TO_CALL); 1228 newSession.mReferSession = this; 1229 newSession.mReferredBy = (ReferredByHeader) event.getRequest() 1230 .getHeader(ReferredByHeader.NAME); 1231 newSession.mReplaces = replacesHeader; 1232 newSession.mPeerProfile = createPeerProfile(referto); 1233 newSession.mProxy.onCallTransferring(newSession, 1234 null); 1235 return true; 1236 } catch (IllegalArgumentException e) { 1237 throw new SipException("createPeerProfile()", e); 1238 } 1239 } 1240 1241 private boolean inCall(EventObject evt) throws SipException { 1242 // expect END_CALL cmd, BYE request, hold call (MakeCallCommand) 1243 // OK retransmission is handled in SipStack 1244 if (END_CALL == evt) { 1245 // rfc3261#section-15.1.1 1246 mState = SipSession.State.ENDING_CALL; 1247 mSipHelper.sendBye(mDialog); 1248 mProxy.onCallEnded(this); 1249 startSessionTimer(END_CALL_TIMER); 1250 return true; 1251 } else if (isRequestEvent(Request.INVITE, evt)) { 1252 // got Re-INVITE 1253 mState = SipSession.State.INCOMING_CALL; 1254 RequestEvent event = mInviteReceived = (RequestEvent) evt; 1255 mPeerSessionDescription = extractContent(event.getRequest()); 1256 mServerTransaction = null; 1257 mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription); 1258 return true; 1259 } else if (isRequestEvent(Request.BYE, evt)) { 1260 mSipHelper.sendResponse((RequestEvent) evt, Response.OK); 1261 endCallNormally(); 1262 return true; 1263 } else if (isRequestEvent(Request.REFER, evt)) { 1264 return processReferRequest((RequestEvent) evt); 1265 } else if (evt instanceof MakeCallCommand) { 1266 // to change call 1267 mState = SipSession.State.OUTGOING_CALL; 1268 mClientTransaction = mSipHelper.sendReinvite(mDialog, 1269 ((MakeCallCommand) evt).getSessionDescription()); 1270 startSessionTimer(((MakeCallCommand) evt).getTimeout()); 1271 return true; 1272 } else if (evt instanceof ResponseEvent) { 1273 if (expectResponse(Request.NOTIFY, evt)) return true; 1274 } 1275 return false; 1276 } 1277 1278 private boolean endingCall(EventObject evt) throws SipException { 1279 if (expectResponse(Request.BYE, evt)) { 1280 ResponseEvent event = (ResponseEvent) evt; 1281 Response response = event.getResponse(); 1282 1283 int statusCode = response.getStatusCode(); 1284 switch (statusCode) { 1285 case Response.UNAUTHORIZED: 1286 case Response.PROXY_AUTHENTICATION_REQUIRED: 1287 if (handleAuthentication(event)) { 1288 return true; 1289 } else { 1290 // can't authenticate; pass through to end session 1291 } 1292 } 1293 cancelSessionTimer(); 1294 reset(); 1295 return true; 1296 } 1297 return false; 1298 } 1299 1300 // timeout in seconds 1301 private void startSessionTimer(int timeout) { 1302 if (timeout > 0) { 1303 mSessionTimer = new SessionTimer(); 1304 mSessionTimer.start(timeout); 1305 } 1306 } 1307 1308 private void cancelSessionTimer() { 1309 if (mSessionTimer != null) { 1310 mSessionTimer.cancel(); 1311 mSessionTimer = null; 1312 } 1313 } 1314 1315 private String createErrorMessage(Response response) { 1316 return String.format("%s (%d)", response.getReasonPhrase(), 1317 response.getStatusCode()); 1318 } 1319 1320 private void enableKeepAlive() { 1321 if (mKeepAliveSession != null) { 1322 mKeepAliveSession.stopKeepAliveProcess(); 1323 } else { 1324 mKeepAliveSession = duplicate(); 1325 } 1326 try { 1327 mKeepAliveSession.startKeepAliveProcess( 1328 INCALL_KEEPALIVE_INTERVAL, mPeerProfile, null); 1329 } catch (SipException e) { 1330 Log.w(TAG, "keepalive cannot be enabled; ignored", e); 1331 mKeepAliveSession.stopKeepAliveProcess(); 1332 } 1333 } 1334 1335 private void establishCall(boolean enableKeepAlive) { 1336 mState = SipSession.State.IN_CALL; 1337 cancelSessionTimer(); 1338 if (!mInCall && enableKeepAlive) enableKeepAlive(); 1339 mInCall = true; 1340 mProxy.onCallEstablished(this, mPeerSessionDescription); 1341 } 1342 1343 private void endCallNormally() { 1344 reset(); 1345 mProxy.onCallEnded(this); 1346 } 1347 1348 private void endCallOnError(int errorCode, String message) { 1349 reset(); 1350 mProxy.onError(this, errorCode, message); 1351 } 1352 1353 private void endCallOnBusy() { 1354 reset(); 1355 mProxy.onCallBusy(this); 1356 } 1357 1358 private void onError(int errorCode, String message) { 1359 cancelSessionTimer(); 1360 switch (mState) { 1361 case SipSession.State.REGISTERING: 1362 case SipSession.State.DEREGISTERING: 1363 onRegistrationFailed(errorCode, message); 1364 break; 1365 default: 1366 endCallOnError(errorCode, message); 1367 } 1368 } 1369 1370 1371 private void onError(Throwable exception) { 1372 exception = getRootCause(exception); 1373 onError(getErrorCode(exception), exception.toString()); 1374 } 1375 1376 private void onError(Response response) { 1377 int statusCode = response.getStatusCode(); 1378 if (!mInCall && (statusCode == Response.BUSY_HERE)) { 1379 endCallOnBusy(); 1380 } else { 1381 onError(getErrorCode(statusCode), createErrorMessage(response)); 1382 } 1383 } 1384 1385 private int getErrorCode(int responseStatusCode) { 1386 switch (responseStatusCode) { 1387 case Response.TEMPORARILY_UNAVAILABLE: 1388 case Response.FORBIDDEN: 1389 case Response.GONE: 1390 case Response.NOT_FOUND: 1391 case Response.NOT_ACCEPTABLE: 1392 case Response.NOT_ACCEPTABLE_HERE: 1393 return SipErrorCode.PEER_NOT_REACHABLE; 1394 1395 case Response.REQUEST_URI_TOO_LONG: 1396 case Response.ADDRESS_INCOMPLETE: 1397 case Response.AMBIGUOUS: 1398 return SipErrorCode.INVALID_REMOTE_URI; 1399 1400 case Response.REQUEST_TIMEOUT: 1401 return SipErrorCode.TIME_OUT; 1402 1403 default: 1404 if (responseStatusCode < 500) { 1405 return SipErrorCode.CLIENT_ERROR; 1406 } else { 1407 return SipErrorCode.SERVER_ERROR; 1408 } 1409 } 1410 } 1411 1412 private Throwable getRootCause(Throwable exception) { 1413 Throwable cause = exception.getCause(); 1414 while (cause != null) { 1415 exception = cause; 1416 cause = exception.getCause(); 1417 } 1418 return exception; 1419 } 1420 1421 private int getErrorCode(Throwable exception) { 1422 String message = exception.getMessage(); 1423 if (exception instanceof UnknownHostException) { 1424 return SipErrorCode.SERVER_UNREACHABLE; 1425 } else if (exception instanceof IOException) { 1426 return SipErrorCode.SOCKET_ERROR; 1427 } else { 1428 return SipErrorCode.CLIENT_ERROR; 1429 } 1430 } 1431 1432 private void onRegistrationDone(int duration) { 1433 reset(); 1434 mProxy.onRegistrationDone(this, duration); 1435 } 1436 1437 private void onRegistrationFailed(int errorCode, String message) { 1438 reset(); 1439 mProxy.onRegistrationFailed(this, errorCode, message); 1440 } 1441 1442 private void onRegistrationFailed(Throwable exception) { 1443 exception = getRootCause(exception); 1444 onRegistrationFailed(getErrorCode(exception), 1445 exception.toString()); 1446 } 1447 1448 private void onRegistrationFailed(Response response) { 1449 int statusCode = response.getStatusCode(); 1450 onRegistrationFailed(getErrorCode(statusCode), 1451 createErrorMessage(response)); 1452 } 1453 1454 // Notes: SipSessionListener will be replaced by the keepalive process 1455 // @param interval in seconds 1456 public void startKeepAliveProcess(int interval, 1457 KeepAliveProcessCallback callback) throws SipException { 1458 synchronized (SipSessionGroup.this) { 1459 startKeepAliveProcess(interval, mLocalProfile, callback); 1460 } 1461 } 1462 1463 // Notes: SipSessionListener will be replaced by the keepalive process 1464 // @param interval in seconds 1465 public void startKeepAliveProcess(int interval, SipProfile peerProfile, 1466 KeepAliveProcessCallback callback) throws SipException { 1467 synchronized (SipSessionGroup.this) { 1468 if (mKeepAliveProcess != null) { 1469 throw new SipException("Cannot create more than one " 1470 + "keepalive process in a SipSession"); 1471 } 1472 mPeerProfile = peerProfile; 1473 mKeepAliveProcess = new KeepAliveProcess(); 1474 mProxy.setListener(mKeepAliveProcess); 1475 mKeepAliveProcess.start(interval, callback); 1476 } 1477 } 1478 1479 public void stopKeepAliveProcess() { 1480 synchronized (SipSessionGroup.this) { 1481 if (mKeepAliveProcess != null) { 1482 mKeepAliveProcess.stop(); 1483 mKeepAliveProcess = null; 1484 } 1485 } 1486 } 1487 1488 class KeepAliveProcess extends SipSessionAdapter implements Runnable { 1489 private static final String TAG = "SipKeepAlive"; 1490 private boolean mRunning = false; 1491 private KeepAliveProcessCallback mCallback; 1492 1493 private boolean mPortChanged = false; 1494 private int mRPort = 0; 1495 private int mInterval; // just for debugging 1496 1497 // @param interval in seconds 1498 void start(int interval, KeepAliveProcessCallback callback) { 1499 if (mRunning) return; 1500 mRunning = true; 1501 mInterval = interval; 1502 mCallback = new KeepAliveProcessCallbackProxy(callback); 1503 mWakeupTimer.set(interval * 1000, this); 1504 if (DEBUG) { 1505 Log.d(TAG, "start keepalive:" 1506 + mLocalProfile.getUriString()); 1507 } 1508 1509 // No need to run the first time in a separate thread for now 1510 run(); 1511 } 1512 1513 // return true if the event is consumed 1514 boolean process(EventObject evt) throws SipException { 1515 if (mRunning && (mState == SipSession.State.PINGING)) { 1516 if (evt instanceof ResponseEvent) { 1517 if (parseOptionsResult(evt)) { 1518 if (mPortChanged) { 1519 resetExternalAddress(); 1520 stop(); 1521 } else { 1522 cancelSessionTimer(); 1523 removeSipSession(SipSessionImpl.this); 1524 } 1525 mCallback.onResponse(mPortChanged); 1526 return true; 1527 } 1528 } 1529 } 1530 return false; 1531 } 1532 1533 // SipSessionAdapter 1534 // To react to the session timeout event and network error. 1535 @Override 1536 public void onError(ISipSession session, int errorCode, String message) { 1537 stop(); 1538 mCallback.onError(errorCode, message); 1539 } 1540 1541 // SipWakeupTimer timeout handler 1542 // To send out keepalive message. 1543 @Override 1544 public void run() { 1545 synchronized (SipSessionGroup.this) { 1546 if (!mRunning) return; 1547 1548 if (DEBUG_PING) { 1549 String peerUri = (mPeerProfile == null) 1550 ? "null" 1551 : mPeerProfile.getUriString(); 1552 Log.d(TAG, "keepalive: " + mLocalProfile.getUriString() 1553 + " --> " + peerUri + ", interval=" + mInterval); 1554 } 1555 try { 1556 sendKeepAlive(); 1557 } catch (Throwable t) { 1558 Log.w(TAG, "keepalive error: " 1559 + mLocalProfile.getUriString(), getRootCause(t)); 1560 // It's possible that the keepalive process is being stopped 1561 // during session.sendKeepAlive() so need to check mRunning 1562 // again here. 1563 if (mRunning) SipSessionImpl.this.onError(t); 1564 } 1565 } 1566 } 1567 1568 void stop() { 1569 synchronized (SipSessionGroup.this) { 1570 if (DEBUG) { 1571 Log.d(TAG, "stop keepalive:" + mLocalProfile.getUriString() 1572 + ",RPort=" + mRPort); 1573 } 1574 mRunning = false; 1575 mWakeupTimer.cancel(this); 1576 reset(); 1577 } 1578 } 1579 1580 private void sendKeepAlive() throws SipException, InterruptedException { 1581 synchronized (SipSessionGroup.this) { 1582 mState = SipSession.State.PINGING; 1583 mClientTransaction = mSipHelper.sendOptions( 1584 mLocalProfile, mPeerProfile, generateTag()); 1585 mDialog = mClientTransaction.getDialog(); 1586 addSipSession(SipSessionImpl.this); 1587 1588 startSessionTimer(KEEPALIVE_TIMEOUT); 1589 // when timed out, onError() will be called with SipErrorCode.TIME_OUT 1590 } 1591 } 1592 1593 private boolean parseOptionsResult(EventObject evt) { 1594 if (expectResponse(Request.OPTIONS, evt)) { 1595 ResponseEvent event = (ResponseEvent) evt; 1596 int rPort = getRPortFromResponse(event.getResponse()); 1597 if (rPort != -1) { 1598 if (mRPort == 0) mRPort = rPort; 1599 if (mRPort != rPort) { 1600 mPortChanged = true; 1601 if (DEBUG) Log.d(TAG, String.format( 1602 "rport is changed: %d <> %d", mRPort, rPort)); 1603 mRPort = rPort; 1604 } else { 1605 if (DEBUG) Log.d(TAG, "rport is the same: " + rPort); 1606 } 1607 } else { 1608 if (DEBUG) Log.w(TAG, "peer did not respond rport"); 1609 } 1610 return true; 1611 } 1612 return false; 1613 } 1614 1615 private int getRPortFromResponse(Response response) { 1616 ViaHeader viaHeader = (ViaHeader)(response.getHeader( 1617 SIPHeaderNames.VIA)); 1618 return (viaHeader == null) ? -1 : viaHeader.getRPort(); 1619 } 1620 } 1621 } 1622 1623 /** 1624 * @return true if the event is a request event matching the specified 1625 * method; false otherwise 1626 */ 1627 private static boolean isRequestEvent(String method, EventObject event) { 1628 try { 1629 if (event instanceof RequestEvent) { 1630 RequestEvent requestEvent = (RequestEvent) event; 1631 return method.equals(requestEvent.getRequest().getMethod()); 1632 } 1633 } catch (Throwable e) { 1634 } 1635 return false; 1636 } 1637 1638 private static String getCseqMethod(Message message) { 1639 return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod(); 1640 } 1641 1642 /** 1643 * @return true if the event is a response event and the CSeqHeader method 1644 * match the given arguments; false otherwise 1645 */ 1646 private static boolean expectResponse( 1647 String expectedMethod, EventObject evt) { 1648 if (evt instanceof ResponseEvent) { 1649 ResponseEvent event = (ResponseEvent) evt; 1650 Response response = event.getResponse(); 1651 return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); 1652 } 1653 return false; 1654 } 1655 1656 /** 1657 * @return true if the event is a response event and the response code and 1658 * CSeqHeader method match the given arguments; false otherwise 1659 */ 1660 private static boolean expectResponse( 1661 int responseCode, String expectedMethod, EventObject evt) { 1662 if (evt instanceof ResponseEvent) { 1663 ResponseEvent event = (ResponseEvent) evt; 1664 Response response = event.getResponse(); 1665 if (response.getStatusCode() == responseCode) { 1666 return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); 1667 } 1668 } 1669 return false; 1670 } 1671 1672 private static SipProfile createPeerProfile(HeaderAddress header) 1673 throws SipException { 1674 try { 1675 Address address = header.getAddress(); 1676 SipURI uri = (SipURI) address.getURI(); 1677 String username = uri.getUser(); 1678 if (username == null) username = ANONYMOUS; 1679 int port = uri.getPort(); 1680 SipProfile.Builder builder = 1681 new SipProfile.Builder(username, uri.getHost()) 1682 .setDisplayName(address.getDisplayName()); 1683 if (port > 0) builder.setPort(port); 1684 return builder.build(); 1685 } catch (IllegalArgumentException e) { 1686 throw new SipException("createPeerProfile()", e); 1687 } catch (ParseException e) { 1688 throw new SipException("createPeerProfile()", e); 1689 } 1690 } 1691 1692 private static boolean isLoggable(SipSessionImpl s) { 1693 if (s != null) { 1694 switch (s.mState) { 1695 case SipSession.State.PINGING: 1696 return DEBUG_PING; 1697 } 1698 } 1699 return DEBUG; 1700 } 1701 1702 private static boolean isLoggable(EventObject evt) { 1703 return isLoggable(null, evt); 1704 } 1705 1706 private static boolean isLoggable(SipSessionImpl s, EventObject evt) { 1707 if (!isLoggable(s)) return false; 1708 if (evt == null) return false; 1709 1710 if (evt instanceof ResponseEvent) { 1711 Response response = ((ResponseEvent) evt).getResponse(); 1712 if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { 1713 return DEBUG_PING; 1714 } 1715 return DEBUG; 1716 } else if (evt instanceof RequestEvent) { 1717 if (isRequestEvent(Request.OPTIONS, evt)) { 1718 return DEBUG_PING; 1719 } 1720 return DEBUG; 1721 } 1722 return false; 1723 } 1724 1725 private static String log(EventObject evt) { 1726 if (evt instanceof RequestEvent) { 1727 return ((RequestEvent) evt).getRequest().toString(); 1728 } else if (evt instanceof ResponseEvent) { 1729 return ((ResponseEvent) evt).getResponse().toString(); 1730 } else { 1731 return evt.toString(); 1732 } 1733 } 1734 1735 private class RegisterCommand extends EventObject { 1736 private int mDuration; 1737 1738 public RegisterCommand(int duration) { 1739 super(SipSessionGroup.this); 1740 mDuration = duration; 1741 } 1742 1743 public int getDuration() { 1744 return mDuration; 1745 } 1746 } 1747 1748 private class MakeCallCommand extends EventObject { 1749 private String mSessionDescription; 1750 private int mTimeout; // in seconds 1751 1752 public MakeCallCommand(SipProfile peerProfile, 1753 String sessionDescription) { 1754 this(peerProfile, sessionDescription, -1); 1755 } 1756 1757 public MakeCallCommand(SipProfile peerProfile, 1758 String sessionDescription, int timeout) { 1759 super(peerProfile); 1760 mSessionDescription = sessionDescription; 1761 mTimeout = timeout; 1762 } 1763 1764 public SipProfile getPeerProfile() { 1765 return (SipProfile) getSource(); 1766 } 1767 1768 public String getSessionDescription() { 1769 return mSessionDescription; 1770 } 1771 1772 public int getTimeout() { 1773 return mTimeout; 1774 } 1775 } 1776 1777 /** Class to help safely run KeepAliveProcessCallback in a different thread. */ 1778 static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback { 1779 private KeepAliveProcessCallback mCallback; 1780 1781 KeepAliveProcessCallbackProxy(KeepAliveProcessCallback callback) { 1782 mCallback = callback; 1783 } 1784 1785 private void proxy(Runnable runnable) { 1786 // One thread for each calling back. 1787 // Note: Guarantee ordering if the issue becomes important. Currently, 1788 // the chance of handling two callback events at a time is none. 1789 new Thread(runnable, "SIP-KeepAliveProcessCallbackThread").start(); 1790 } 1791 1792 public void onResponse(final boolean portChanged) { 1793 if (mCallback == null) return; 1794 proxy(new Runnable() { 1795 public void run() { 1796 try { 1797 mCallback.onResponse(portChanged); 1798 } catch (Throwable t) { 1799 Log.w(TAG, "onResponse", t); 1800 } 1801 } 1802 }); 1803 } 1804 1805 public void onError(final int errorCode, final String description) { 1806 if (mCallback == null) return; 1807 proxy(new Runnable() { 1808 public void run() { 1809 try { 1810 mCallback.onError(errorCode, description); 1811 } catch (Throwable t) { 1812 Log.w(TAG, "onError", t); 1813 } 1814 } 1815 }); 1816 } 1817 } 1818 } 1819