• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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