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