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