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