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