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