SipSessionGroup.java revision 1aceda35cc607856ec2e960e0c6cfc6aea87ab8e
12d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang/*
22d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * Copyright (C) 2010 The Android Open Source Project
32d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang *
42d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * Licensed under the Apache License, Version 2.0 (the "License");
52d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * you may not use this file except in compliance with the License.
62d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * You may obtain a copy of the License at
72d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang *
82d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang *      http://www.apache.org/licenses/LICENSE-2.0
92d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang *
102d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * Unless required by applicable law or agreed to in writing, software
112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * distributed under the License is distributed on an "AS IS" BASIS,
122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * See the License for the specific language governing permissions and
142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * limitations under the License.
152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang */
162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangpackage com.android.server.sip;
182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport gov.nist.javax.sip.clientauthutils.AccountManager;
202d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport gov.nist.javax.sip.clientauthutils.UserCredentials;
212d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport gov.nist.javax.sip.header.SIPHeaderNames;
227d137e40cd36290c6bfb5beaf66f4018ae92c97fChung-yih Wangimport gov.nist.javax.sip.header.ProxyAuthenticate;
232d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport gov.nist.javax.sip.header.WWWAuthenticate;
241aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo syncimport gov.nist.javax.sip.header.extensions.ReferredByHeader;
251aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo syncimport gov.nist.javax.sip.header.extensions.ReplacesHeader;
2695b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yehimport gov.nist.javax.sip.message.SIPMessage;
272d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
282d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport android.net.sip.ISipSession;
292d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport android.net.sip.ISipSessionListener;
30903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyanimport android.net.sip.SipErrorCode;
312d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport android.net.sip.SipProfile;
3284a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyanimport android.net.sip.SipSession;
334a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyanimport android.net.sip.SipSessionAdapter;
342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport android.text.TextUtils;
352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport android.util.Log;
362d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
372d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.io.IOException;
3895b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yehimport java.io.UnsupportedEncodingException;
392d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.net.DatagramSocket;
40903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyanimport java.net.UnknownHostException;
412d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.text.ParseException;
422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.util.Collection;
432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.util.EventObject;
442d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.util.HashMap;
452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.util.Map;
462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.util.Properties;
472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport java.util.TooManyListenersException;
482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.ClientTransaction;
502d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.Dialog;
512d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.DialogTerminatedEvent;
522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.IOExceptionEvent;
532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.InvalidArgumentException;
542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.ListeningPoint;
559ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyanimport javax.sip.ObjectInUseException;
562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.RequestEvent;
572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.ResponseEvent;
582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.ServerTransaction;
592d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.SipException;
602d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.SipFactory;
612d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.SipListener;
622d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.SipProvider;
632d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.SipStack;
642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.TimeoutEvent;
652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.Transaction;
662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.TransactionState;
672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.TransactionTerminatedEvent;
68903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyanimport javax.sip.TransactionUnavailableException;
692d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.address.Address;
702d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.address.SipURI;
712d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.header.CSeqHeader;
722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.header.ExpiresHeader;
732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.header.FromHeader;
742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.header.MinExpiresHeader;
752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.header.ViaHeader;
762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.message.Message;
772d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.message.Request;
782d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangimport javax.sip.message.Response;
792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
802d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang/**
812d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang * Manages {@link ISipSession}'s for a SIP account.
822d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang */
832d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wangclass SipSessionGroup implements SipListener {
842d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static final String TAG = "SipSession";
85c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan    private static final boolean DEBUG = true;
86c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan    private static final boolean DEBUG_PING = DEBUG && false;
872d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static final String ANONYMOUS = "anonymous";
8866cc5355a137e291cc1e3c5d871e1d9cd35ee0abChung-yih Wang    // Limit the size of thread pool to 1 for the order issue when the phone is
8966cc5355a137e291cc1e3c5d871e1d9cd35ee0abChung-yih Wang    // waken up from sleep and there are many packets to be processed in the SIP
9066cc5355a137e291cc1e3c5d871e1d9cd35ee0abChung-yih Wang    // stack. Note: The default thread pool size in NIST SIP stack is -1 which is
9166cc5355a137e291cc1e3c5d871e1d9cd35ee0abChung-yih Wang    // unlimited.
9266cc5355a137e291cc1e3c5d871e1d9cd35ee0abChung-yih Wang    private static final String THREAD_POOL_SIZE = "1";
939352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan    private static final int EXPIRY_TIME = 3600; // in seconds
94194bbcce9ba15634500f542b9ea017b2cf154b45Hung-ying Tyan    private static final int CANCEL_CALL_TIMER = 3; // in seconds
954a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    private static final int KEEPALIVE_TIMEOUT = 3; // in seconds
9628f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan    private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds
972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static final EventObject DEREGISTER = new EventObject("Deregister");
992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static final EventObject END_CALL = new EventObject("End call");
1002d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static final EventObject HOLD_CALL = new EventObject("Hold call");
1012d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static final EventObject CONTINUE_CALL
1022d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            = new EventObject("Continue call");
1032d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1042d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private final SipProfile mLocalProfile;
1052d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private final String mPassword;
1062d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1072d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private SipStack mSipStack;
1082d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private SipHelper mSipHelper;
1092d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1102d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    // session that processes INVITE requests
1112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private SipSessionImpl mCallReceiverSession;
1122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private String mLocalIp;
1132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1144a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    private SipWakeupTimer mWakeupTimer;
11528f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan    private SipWakeLock mWakeLock;
11628f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan
1172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    // call-id-to-SipSession map
1182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private Map<String, SipSessionImpl> mSessionMap =
1192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            new HashMap<String, SipSessionImpl>();
1202d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1212d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    /**
1222d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     * @param myself the local profile with password crossed out
1232d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     * @param password the password of the profile
1242d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     * @throws IOException if cannot assign requested address
1252d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     */
12628f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan    public SipSessionGroup(String localIp, SipProfile myself, String password,
1274a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException,
1284a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            IOException {
1292d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        mLocalProfile = myself;
1302d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        mPassword = password;
1314a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        mWakeupTimer = timer;
13228f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan        mWakeLock = wakeLock;
1332d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        reset(localIp);
1342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
1352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1364a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    // TODO: remove this method once SipWakeupTimer can better handle variety
1374a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    // of timeout values
1384a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    void setWakeupTimer(SipWakeupTimer timer) {
1394a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        mWakeupTimer = timer;
1404a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    }
1414a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
14284a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan    synchronized void reset(String localIp) throws SipException, IOException {
1432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        mLocalIp = localIp;
1442d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if (localIp == null) return;
1452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipProfile myself = mLocalProfile;
1472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipFactory sipFactory = SipFactory.getInstance();
1482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        Properties properties = new Properties();
1492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        properties.setProperty("javax.sip.STACK_NAME", getStackName());
150b4116c09fb9784551911ea0a10b4dd321ff9aa7dChung-yih Wang        properties.setProperty(
151b4116c09fb9784551911ea0a10b4dd321ff9aa7dChung-yih Wang                "gov.nist.javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE);
1522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        String outboundProxy = myself.getProxyAddress();
1532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if (!TextUtils.isEmpty(outboundProxy)) {
1545de1d36dd0415c4cf9afdf093a4915951ef6c770Chung-yih Wang            Log.v(TAG, "outboundProxy is " + outboundProxy);
1552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy
1562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    + ":" + myself.getPort() + "/" + myself.getProtocol());
1572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
1582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipStack stack = mSipStack = sipFactory.createSipStack(properties);
1592d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1602d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        try {
1612d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            SipProvider provider = stack.createSipProvider(
1622d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    stack.createListeningPoint(localIp, allocateLocalPort(),
1632d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                            myself.getProtocol()));
1642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            provider.addSipListener(this);
1652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mSipHelper = new SipHelper(stack, provider);
1662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } catch (InvalidArgumentException e) {
1672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            throw new IOException(e.getMessage());
1682d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } catch (TooManyListenersException e) {
1692d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            // must never happen
1702d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            throw new SipException("SipSessionGroup constructor", e);
1712d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
1722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        Log.d(TAG, " start stack for " + myself.getUriString());
1732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        stack.start();
1742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        mCallReceiverSession = null;
1762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        mSessionMap.clear();
1772d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
1782d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
179d231aa880ab006d51ffe03454c1fc082f1c97bb8Hung-ying Tyan    synchronized void onConnectivityChanged() {
180ebc886c857a702d788c2bdfb9e0b3d20746ad745Hung-ying Tyan        SipSessionImpl[] ss = mSessionMap.values().toArray(
181ebc886c857a702d788c2bdfb9e0b3d20746ad745Hung-ying Tyan                    new SipSessionImpl[mSessionMap.size()]);
182ebc886c857a702d788c2bdfb9e0b3d20746ad745Hung-ying Tyan        // Iterate on the copied array instead of directly on mSessionMap to
183ebc886c857a702d788c2bdfb9e0b3d20746ad745Hung-ying Tyan        // avoid ConcurrentModificationException being thrown when
184ebc886c857a702d788c2bdfb9e0b3d20746ad745Hung-ying Tyan        // SipSessionImpl removes itself from mSessionMap in onError() in the
185ebc886c857a702d788c2bdfb9e0b3d20746ad745Hung-ying Tyan        // following loop.
186ebc886c857a702d788c2bdfb9e0b3d20746ad745Hung-ying Tyan        for (SipSessionImpl s : ss) {
187d231aa880ab006d51ffe03454c1fc082f1c97bb8Hung-ying Tyan            s.onError(SipErrorCode.DATA_CONNECTION_LOST,
188d231aa880ab006d51ffe03454c1fc082f1c97bb8Hung-ying Tyan                    "data connection lost");
189d231aa880ab006d51ffe03454c1fc082f1c97bb8Hung-ying Tyan        }
190d231aa880ab006d51ffe03454c1fc082f1c97bb8Hung-ying Tyan    }
191d231aa880ab006d51ffe03454c1fc082f1c97bb8Hung-ying Tyan
1922d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public SipProfile getLocalProfile() {
1932d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return mLocalProfile;
1942d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
1952d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1962d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public String getLocalProfileUri() {
1972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return mLocalProfile.getUriString();
1982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
1992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2002d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private String getStackName() {
2012d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return "stack" + System.currentTimeMillis();
2022d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2032d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2042d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public synchronized void close() {
2052d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        Log.d(TAG, " close stack for " + mLocalProfile.getUriString());
2065d0c5cf2d6c6e82bcdce95d72d9000a934b2f354Hung-ying Tyan        onConnectivityChanged();
2072d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        mSessionMap.clear();
2082d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        closeToNotReceiveCalls();
2092d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if (mSipStack != null) {
2102d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mSipStack.stop();
2112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mSipStack = null;
2122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mSipHelper = null;
2132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
2142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public synchronized boolean isClosed() {
2172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return (mSipStack == null);
2182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2202d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    // For internal use, require listener not to block in callbacks.
2212d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public synchronized void openToReceiveCalls(ISipSessionListener listener) {
2222d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if (mCallReceiverSession == null) {
2232d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mCallReceiverSession = new SipSessionCallReceiverImpl(listener);
2242d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } else {
2252d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mCallReceiverSession.setListener(listener);
2262d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
2272d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2282d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2292d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public synchronized void closeToNotReceiveCalls() {
2302d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        mCallReceiverSession = null;
2312d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2322d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2332d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public ISipSession createSession(ISipSessionListener listener) {
2342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return (isClosed() ? null : new SipSessionImpl(listener));
2352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2362d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2372d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static int allocateLocalPort() throws SipException {
2382d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        try {
2392d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            DatagramSocket s = new DatagramSocket();
2402d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            int localPort = s.getLocalPort();
2412d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            s.close();
2422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return localPort;
2432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } catch (IOException e) {
2442d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            throw new SipException("allocateLocalPort()", e);
2452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
2462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2480a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan    synchronized boolean containsSession(String callId) {
2490a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan        return mSessionMap.containsKey(callId);
2500a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan    }
2510a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan
2522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private synchronized SipSessionImpl getSipSession(EventObject event) {
2532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        String key = SipHelper.getCallId(event);
2542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipSessionImpl session = mSessionMap.get(key);
255c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        if ((session != null) && isLoggable(session)) {
256c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            Log.d(TAG, "session key from event: " + key);
257c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            Log.d(TAG, "active sessions:");
258c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            for (String k : mSessionMap.keySet()) {
259c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan                Log.d(TAG, " ..." + k + ": " + mSessionMap.get(k));
260c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            }
261c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        }
2622d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return ((session != null) ? session : mCallReceiverSession);
2632d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private synchronized void addSipSession(SipSessionImpl newSession) {
2662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        removeSipSession(newSession);
2672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        String key = newSession.getCallId();
2682d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        mSessionMap.put(key, newSession);
269c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        if (isLoggable(newSession)) {
270c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            Log.d(TAG, "+++  add a session with key:  '" + key + "'");
271c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            for (String k : mSessionMap.keySet()) {
272c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan                Log.d(TAG, "  " + k + ": " + mSessionMap.get(k));
273c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            }
2742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
2752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
2762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
2772d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private synchronized void removeSipSession(SipSessionImpl session) {
2782d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if (session == mCallReceiverSession) return;
2792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        String key = session.getCallId();
2802d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipSessionImpl s = mSessionMap.remove(key);
2812d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        // sanity check
2822d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if ((s != null) && (s != session)) {
2832d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            Log.w(TAG, "session " + session + " is not associated with key '"
2842d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    + key + "'");
2852d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mSessionMap.put(key, s);
2862d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            for (Map.Entry<String, SipSessionImpl> entry
2872d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    : mSessionMap.entrySet()) {
2882d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                if (entry.getValue() == s) {
2892d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    key = entry.getKey();
2902d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    mSessionMap.remove(key);
2912d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
2922d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
2932d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
2942d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
295c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        if ((s != null) && isLoggable(s)) {
296c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            Log.d(TAG, "remove session " + session + " @key '" + key + "'");
297c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            for (String k : mSessionMap.keySet()) {
298c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan                Log.d(TAG, "  " + k + ": " + mSessionMap.get(k));
299c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            }
3002d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
3012d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
3022d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
30328f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan    public void processRequest(final RequestEvent event) {
30428f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan        if (isRequestEvent(Request.INVITE, event)) {
30528f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan            if (DEBUG) Log.d(TAG, "<<<<< got INVITE, thread:"
30628f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan                    + Thread.currentThread());
30728f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan            // Acquire a wake lock and keep it for WAKE_LOCK_HOLDING_TIME;
30828f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan            // should be large enough to bring up the app.
30928f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan            mWakeLock.acquire(WAKE_LOCK_HOLDING_TIME);
31028f63c06894b9ca9252f43bc54a098c0a785d4b4Hung-ying Tyan        }
3112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        process(event);
3122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
3132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
3142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public void processResponse(ResponseEvent event) {
3152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        process(event);
3162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
3172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
3182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public void processIOException(IOExceptionEvent event) {
3192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        process(event);
3202d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
3212d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
3222d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public void processTimeout(TimeoutEvent event) {
3232d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        process(event);
3242d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
3252d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
3262d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public void processTransactionTerminated(TransactionTerminatedEvent event) {
3272d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        process(event);
3282d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
3292d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
3302d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    public void processDialogTerminated(DialogTerminatedEvent event) {
3312d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        process(event);
3322d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
3332d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
3342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private synchronized void process(EventObject event) {
3352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipSessionImpl session = getSipSession(event);
3362d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        try {
337c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            boolean isLoggable = isLoggable(session, event);
338c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            boolean processed = (session != null) && session.process(event);
339c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            if (isLoggable && processed) {
34097963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                Log.d(TAG, "new state after: "
34184a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                        + SipSession.State.toString(session.mState));
3422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
3432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } catch (Throwable e) {
344903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            Log.w(TAG, "event process error: " + event, e);
3452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            session.onError(e);
3462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
3472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
3482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
34995b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh    private String extractContent(Message message) {
35095b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh        // Currently we do not support secure MIME bodies.
35195b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh        byte[] bytes = message.getRawContent();
35295b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh        if (bytes != null) {
35395b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh            try {
35495b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                if (message instanceof SIPMessage) {
35595b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                    return ((SIPMessage) message).getMessageContent();
35695b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                } else {
35795b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                    return new String(bytes, "UTF-8");
35895b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                }
35995b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh            } catch (UnsupportedEncodingException e) {
36095b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh            }
36195b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh        }
36295b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh        return null;
36395b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh    }
36495b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh
3652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private class SipSessionCallReceiverImpl extends SipSessionImpl {
3662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public SipSessionCallReceiverImpl(ISipSessionListener listener) {
3672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            super(listener);
3682d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
3692d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
3701aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync        private SipSessionImpl createNewSession(RequestEvent event,
3711aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                ISipSessionListener listener, ServerTransaction transaction)
3721aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                throws SipException {
3731aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            SipSessionImpl newSession = new SipSessionImpl(listener);
3741aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            newSession.mServerTransaction = transaction;
3751aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            newSession.mState = SipSession.State.INCOMING_CALL;
3761aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            newSession.mDialog = newSession.mServerTransaction.getDialog();
3771aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            newSession.mInviteReceived = event;
3781aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            newSession.mPeerProfile = createPeerProfile(event.getRequest());
3791aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            newSession.mPeerSessionDescription =
3801aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    extractContent(event.getRequest());
3811aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            return newSession;
3821aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync        }
3831aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync
3841aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync        private int processInviteWithReplaces(RequestEvent event,
3851aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                ReplacesHeader replaces) {
3861aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            String callId = replaces.getCallId();
3871aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            SipSessionImpl session = mSessionMap.get(callId);
3881aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            if (session == null) {
3891aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
3901aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            }
3911aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync
3921aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            Dialog dialog = session.mDialog;
3931aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            if (dialog == null) return Response.DECLINE;
3941aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync
3951aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            if (!dialog.getLocalTag().equals(replaces.getToTag()) ||
3961aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    !dialog.getRemoteTag().equals(replaces.getFromTag())) {
3971aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                // No match is found, returns 481.
3981aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
3991aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            }
4001aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync
4011aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            ReferredByHeader referredBy = (ReferredByHeader) event.getRequest()
4021aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    .getHeader(ReferredByHeader.NAME);
4031aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            if ((referredBy == null) ||
4041aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    !dialog.getRemoteParty().equals(referredBy.getAddress())) {
4051aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
4061aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            }
4071aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            return Response.OK;
4081aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync        }
4091aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync
4101aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync        private void processNewInviteRequest(RequestEvent event)
4111aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                throws SipException {
4121aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            ReplacesHeader replaces = (ReplacesHeader) event.getRequest()
4131aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    .getHeader(ReplacesHeader.NAME);
4141aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            SipSessionImpl newSession = null;
4151aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            if (replaces != null) {
4161aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                int response = processInviteWithReplaces(event, replaces);
4171aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                if (DEBUG) {
4181aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    Log.v(TAG, "ReplacesHeader: " + replaces
4191aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                            + " response=" + response);
4201aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                }
4211aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                if (response == Response.OK) {
4221aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    SipSessionImpl replacedSession =
4231aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                            mSessionMap.get(replaces.getCallId());
4241aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    // got INVITE w/ replaces request.
4251aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    newSession = createNewSession(event,
4261aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                            replacedSession.mProxy.getListener(),
4271aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                            mSipHelper.getServerTransaction(event));
4281aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    newSession.mProxy.onCallTransferring(newSession,
4291aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                            newSession.mPeerSessionDescription);
4301aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                } else {
4311aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                    mSipHelper.sendResponse(event, response);
4321aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                }
4331aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            } else {
4341aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                // New Incoming call.
4351aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                newSession = createNewSession(event, mProxy,
4361aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                        mSipHelper.sendRinging(event, generateTag()));
4371aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                mProxy.onRinging(newSession, newSession.mPeerProfile,
4381aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                        newSession.mPeerSessionDescription);
4391aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            }
4401aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync            if (newSession != null) addSipSession(newSession);
4411aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync        }
4421aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync
4432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public boolean process(EventObject evt) throws SipException {
444c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~   " + this + ": "
44584a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                    + SipSession.State.toString(mState) + ": processing "
44697963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                    + log(evt));
4472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (isRequestEvent(Request.INVITE, evt)) {
4481aceda35cc607856ec2e960e0c6cfc6aea87ab8erepo sync                processNewInviteRequest((RequestEvent) evt);
4492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
4500b4d2fb11405e2e785ec30cabe7bb311c654c0d2Chung-yih Wang            } else if (isRequestEvent(Request.OPTIONS, evt)) {
4510b4d2fb11405e2e785ec30cabe7bb311c654c0d2Chung-yih Wang                mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
4520b4d2fb11405e2e785ec30cabe7bb311c654c0d2Chung-yih Wang                return true;
4532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else {
4542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return false;
4552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
4562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
4572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
4582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
4594a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    static interface KeepAliveProcessCallback {
4604a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        /** Invoked when the response of keeping alive comes back. */
4614a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        void onResponse(boolean portChanged);
4624a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        void onError(int errorCode, String description);
4634a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    }
4644a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
4652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    class SipSessionImpl extends ISipSession.Stub {
4662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipProfile mPeerProfile;
4672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
46884a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan        int mState = SipSession.State.READY_TO_CALL;
4692d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        RequestEvent mInviteReceived;
4702d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        Dialog mDialog;
4712d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        ServerTransaction mServerTransaction;
4722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        ClientTransaction mClientTransaction;
47395b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh        String mPeerSessionDescription;
4742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        boolean mInCall;
4754a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        SessionTimer mSessionTimer;
476ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan        int mAuthenticationRetryCount;
477ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan
4784a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        private KeepAliveProcess mKeepAliveProcess;
4799352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
4809352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        // lightweight timer
4819352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        class SessionTimer {
4829352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            private boolean mRunning = true;
4839352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
4849352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            void start(final int timeout) {
4859352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                new Thread(new Runnable() {
4869352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                    public void run() {
4879352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                        sleep(timeout);
4889352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                        if (mRunning) timeout();
4899352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                    }
49084a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                }, "SipSessionTimerThread").start();
4919352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            }
4929352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
4939352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            synchronized void cancel() {
4949352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                mRunning = false;
4959352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                this.notify();
4969352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            }
4979352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
4989352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            private void timeout() {
4999352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                synchronized (SipSessionGroup.this) {
5009352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                    onError(SipErrorCode.TIME_OUT, "Session timed out!");
5019352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                }
5029352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            }
5039352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
5049352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            private synchronized void sleep(int timeout) {
5059352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                try {
5069352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                    this.wait(timeout * 1000);
5079352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                } catch (InterruptedException e) {
5089352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                    Log.e(TAG, "session timer interrupted!");
5099352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                }
5109352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            }
5119352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        }
5122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public SipSessionImpl(ISipSessionListener listener) {
5142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            setListener(listener);
5152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        SipSessionImpl duplicate() {
5182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return new SipSessionImpl(mProxy.getListener());
5192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5202d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5212d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private void reset() {
5222d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mInCall = false;
5232d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            removeSipSession(this);
5242d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mPeerProfile = null;
52584a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            mState = SipSession.State.READY_TO_CALL;
5262d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mInviteReceived = null;
5279ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan            mPeerSessionDescription = null;
528ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan            mAuthenticationRetryCount = 0;
5299ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan
5309ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan            if (mDialog != null) mDialog.delete();
5312d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mDialog = null;
5329ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan
5339ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan            try {
5349ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan                if (mServerTransaction != null) mServerTransaction.terminate();
5359ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan            } catch (ObjectInUseException e) {
5369ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan                // ignored
5379ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan            }
5382d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mServerTransaction = null;
5399ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan
5409ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan            try {
5419ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan                if (mClientTransaction != null) mClientTransaction.terminate();
5429ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan            } catch (ObjectInUseException e) {
5439ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan                // ignored
5449ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan            }
5452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mClientTransaction = null;
5469352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
5479352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            cancelSessionTimer();
5482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5502d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public boolean isInCall() {
5512d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return mInCall;
5522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public String getLocalIp() {
5552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return mLocalIp;
5562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public SipProfile getLocalProfile() {
5592d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return mLocalProfile;
5602d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5612d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5622d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public SipProfile getPeerProfile() {
5632d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return mPeerProfile;
5642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public String getCallId() {
5672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return SipHelper.getCallId(getTransaction());
5682d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5692d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5702d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private Transaction getTransaction() {
5712d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (mClientTransaction != null) return mClientTransaction;
5722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (mServerTransaction != null) return mServerTransaction;
5732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return null;
5742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
57697963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan        public int getState() {
57797963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan            return mState;
5782d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
5802d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public void setListener(ISipSessionListener listener) {
5812d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mProxy.setListener((listener instanceof SipSessionListenerProxy)
5822d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    ? ((SipSessionListenerProxy) listener).getListener()
5832d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    : listener);
5842d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
5852d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
586dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan        // process the command in a new thread
587dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan        private void doCommandAsync(final EventObject command) {
588dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan            new Thread(new Runnable() {
589dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan                    public void run() {
590dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan                        try {
591dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan                            processCommand(command);
592c6548fd9eda7b58f5a2e2a9c01e3c7cafd42fafbHung-ying Tyan                        } catch (Throwable e) {
5934a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            Log.w(TAG, "command error: " + command + ": "
5944a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                                    + mLocalProfile.getUriString(),
5954a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                                    getRootCause(e));
5963d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                            onError(e);
597dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan                        }
598dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan                    }
59984a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            }, "SipSessionAsyncCmdThread").start();
600dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan        }
601dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan
6029352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        public void makeCall(SipProfile peerProfile, String sessionDescription,
6039352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                int timeout) {
6049352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription,
6059352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                    timeout));
6062d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6072d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6089352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        public void answerCall(String sessionDescription, int timeout) {
60906e8cdc0f81ead604d5adf9d7b3f982e10226fd2Hung-ying Tyan            synchronized (SipSessionGroup.this) {
61006e8cdc0f81ead604d5adf9d7b3f982e10226fd2Hung-ying Tyan                if (mPeerProfile == null) return;
611c133781723f64d1321685d02ad6a208286bf0a42repo sync                doCommandAsync(new MakeCallCommand(mPeerProfile,
612c133781723f64d1321685d02ad6a208286bf0a42repo sync                        sessionDescription, timeout));
6132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
6142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public void endCall() {
617dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan            doCommandAsync(END_CALL);
6182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6209352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        public void changeCall(String sessionDescription, int timeout) {
62106e8cdc0f81ead604d5adf9d7b3f982e10226fd2Hung-ying Tyan            synchronized (SipSessionGroup.this) {
62206e8cdc0f81ead604d5adf9d7b3f982e10226fd2Hung-ying Tyan                if (mPeerProfile == null) return;
62306e8cdc0f81ead604d5adf9d7b3f982e10226fd2Hung-ying Tyan                doCommandAsync(new MakeCallCommand(mPeerProfile,
62406e8cdc0f81ead604d5adf9d7b3f982e10226fd2Hung-ying Tyan                        sessionDescription, timeout));
62506e8cdc0f81ead604d5adf9d7b3f982e10226fd2Hung-ying Tyan            }
6262d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6272d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6282d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public void register(int duration) {
629dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan            doCommandAsync(new RegisterCommand(duration));
6302d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6312d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6322d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public void unregister() {
633dba514c6d8e8263d4b8f31cb2fdebfc1d4f84c35Hung-ying Tyan            doCommandAsync(DEREGISTER);
6342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6362d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private void processCommand(EventObject command) throws SipException {
6370a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan            if (isLoggable(command)) Log.d(TAG, "process cmd: " + command);
6382d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (!process(command)) {
6393d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                onError(SipErrorCode.IN_PROGRESS,
6403d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                        "cannot initiate a new transaction to execute: "
6413d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                        + command);
6422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
6432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6442d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        protected String generateTag() {
6462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            // 32-bit randomness
6472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return String.valueOf((long) (Math.random() * 0x100000000L));
6482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6502d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public String toString() {
6512d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            try {
6522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                String s = super.toString();
65397963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                return s.substring(s.indexOf("@")) + ":"
65484a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                        + SipSession.State.toString(mState);
6552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } catch (Throwable e) {
6562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return super.toString();
6572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
6582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
6592d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6602d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public boolean process(EventObject evt) throws SipException {
661c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~   " + this + ": "
66284a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                    + SipSession.State.toString(mState) + ": processing "
66397963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                    + log(evt));
6642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            synchronized (SipSessionGroup.this) {
6652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                if (isClosed()) return false;
6662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6674a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                if (mKeepAliveProcess != null) {
6684a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    // event consumed by keepalive process
6694a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    if (mKeepAliveProcess.process(evt)) return true;
6704a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
6714a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
6722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                Dialog dialog = null;
6732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                if (evt instanceof RequestEvent) {
6742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    dialog = ((RequestEvent) evt).getDialog();
6752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                } else if (evt instanceof ResponseEvent) {
6762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    dialog = ((ResponseEvent) evt).getDialog();
6772d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
6782d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                if (dialog != null) mDialog = dialog;
6792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6802d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                boolean processed;
6812d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
6822d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                switch (mState) {
68384a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.REGISTERING:
68484a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.DEREGISTERING:
6852d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    processed = registeringToReady(evt);
6862d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    break;
68784a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.READY_TO_CALL:
6882d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    processed = readyForCall(evt);
6892d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    break;
69084a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.INCOMING_CALL:
6912d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    processed = incomingCall(evt);
6922d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    break;
69384a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.INCOMING_CALL_ANSWERING:
6942d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    processed = incomingCallToInCall(evt);
6952d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    break;
69684a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.OUTGOING_CALL:
69784a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.OUTGOING_CALL_RING_BACK:
6982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    processed = outgoingCall(evt);
6992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    break;
70084a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.OUTGOING_CALL_CANCELING:
7012d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    processed = outgoingCallToReady(evt);
7022d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    break;
70384a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.IN_CALL:
7042d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    processed = inCall(evt);
7052d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    break;
7062d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                default:
7072d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    processed = false;
7082d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
7092d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return (processed || processExceptions(evt));
7102d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
7112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
7122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
7132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private boolean processExceptions(EventObject evt) throws SipException {
7142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (isRequestEvent(Request.BYE, evt)) {
7152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // terminate the call whenever a BYE is received
7162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
7172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                endCallNormally();
7182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
7192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (isRequestEvent(Request.CANCEL, evt)) {
7202d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mSipHelper.sendResponse((RequestEvent) evt,
7212d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
7222d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
7232d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (evt instanceof TransactionTerminatedEvent) {
724025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                if (isCurrentTransaction((TransactionTerminatedEvent) evt)) {
725025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    if (evt instanceof TimeoutEvent) {
726025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                        processTimeout((TimeoutEvent) evt);
727025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    } else {
728025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                        processTransactionTerminated(
729025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                                (TransactionTerminatedEvent) evt);
730025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    }
731025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    return true;
7322d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
7330b4d2fb11405e2e785ec30cabe7bb311c654c0d2Chung-yih Wang            } else if (isRequestEvent(Request.OPTIONS, evt)) {
7340b4d2fb11405e2e785ec30cabe7bb311c654c0d2Chung-yih Wang                mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
7350b4d2fb11405e2e785ec30cabe7bb311c654c0d2Chung-yih Wang                return true;
7362d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (evt instanceof DialogTerminatedEvent) {
7372d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                processDialogTerminated((DialogTerminatedEvent) evt);
7382d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
7392d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
7402d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return false;
7412d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
7422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
7432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private void processDialogTerminated(DialogTerminatedEvent event) {
7442d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (mDialog == event.getDialog()) {
7452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                onError(new SipException("dialog terminated"));
7462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else {
7472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                Log.d(TAG, "not the current dialog; current=" + mDialog
7482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        + ", terminated=" + event.getDialog());
7492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
7502d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
7512d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
752025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan        private boolean isCurrentTransaction(TransactionTerminatedEvent event) {
753025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            Transaction current = event.isServerTransaction()
754025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    ? mServerTransaction
755025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    : mClientTransaction;
756025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            Transaction target = event.isServerTransaction()
757025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    ? event.getServerTransaction()
758025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    : event.getClientTransaction();
759025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan
760025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            if ((current != target) && (mState != SipSession.State.PINGING)) {
761025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                Log.d(TAG, "not the current transaction; current="
762025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                        + toString(current) + ", target=" + toString(target));
763025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                return false;
764025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            } else if (current != null) {
765025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                Log.d(TAG, "transaction terminated: " + toString(current));
766025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                return true;
767fccd5bc78f94b7dcfbcf78ddca83719c9cd1a74fHung-ying Tyan            } else {
768fccd5bc78f94b7dcfbcf78ddca83719c9cd1a74fHung-ying Tyan                // no transaction; shouldn't be here; ignored
769fccd5bc78f94b7dcfbcf78ddca83719c9cd1a74fHung-ying Tyan                return true;
770025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            }
771025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan        }
772025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan
773025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan        private String toString(Transaction transaction) {
774025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            if (transaction == null) return "null";
775025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            Request request = transaction.getRequest();
776025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            Dialog dialog = transaction.getDialog();
777025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME);
778025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(),
779025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    cseq.getSeqNumber(), transaction.getState(),
780025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    ((dialog == null) ? "-" : dialog.getState()));
781025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan        }
782025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan
7833d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan        private void processTransactionTerminated(
7843d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                TransactionTerminatedEvent event) {
7853d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            switch (mState) {
78684a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.IN_CALL:
78784a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.READY_TO_CALL:
7883d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    Log.d(TAG, "Transaction terminated; do nothing");
7893d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    break;
7903d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                default:
7913d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    Log.d(TAG, "Transaction terminated early: " + this);
7923d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    onError(SipErrorCode.TRANSACTION_TERMINTED,
7933d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                            "transaction terminated");
7943d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            }
7953d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan        }
7963d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan
7972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private void processTimeout(TimeoutEvent event) {
798025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan            Log.d(TAG, "processing Timeout...");
7992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            switch (mState) {
80084a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.REGISTERING:
80184a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.DEREGISTERING:
8023d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    reset();
8033d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    mProxy.onRegistrationTimeout(this);
8043d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    break;
80584a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.INCOMING_CALL:
80684a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.INCOMING_CALL_ANSWERING:
80784a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.OUTGOING_CALL:
80884a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.OUTGOING_CALL_CANCELING:
8093d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    onError(SipErrorCode.TIME_OUT, event.toString());
8103d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    break;
8112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
8123d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                default:
8133d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    Log.d(TAG, "   do nothing");
8143d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    break;
8152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
8162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
8172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
8182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private int getExpiryTime(Response response) {
8192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            int expires = EXPIRY_TIME;
8202d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            ExpiresHeader expiresHeader = (ExpiresHeader)
8212d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    response.getHeader(ExpiresHeader.NAME);
8222d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (expiresHeader != null) expires = expiresHeader.getExpires();
8232d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            expiresHeader = (ExpiresHeader)
8242d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    response.getHeader(MinExpiresHeader.NAME);
8252d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (expiresHeader != null) {
8262d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                expires = Math.max(expires, expiresHeader.getExpires());
8272d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
8282d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return expires;
8292d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
8302d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
8312d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private boolean registeringToReady(EventObject evt)
8322d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                throws SipException {
8332d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (expectResponse(Request.REGISTER, evt)) {
8342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                ResponseEvent event = (ResponseEvent) evt;
8352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                Response response = event.getResponse();
8362d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
8372d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                int statusCode = response.getStatusCode();
8382d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                switch (statusCode) {
8392d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                case Response.OK:
84097963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                    int state = mState;
84184a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                    onRegistrationDone((state == SipSession.State.REGISTERING)
8422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                            ? getExpiryTime(((ResponseEvent) evt).getResponse())
8432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                            : -1);
8442d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return true;
8452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                case Response.UNAUTHORIZED:
8462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                case Response.PROXY_AUTHENTICATION_REQUIRED:
847ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                    handleAuthentication(event);
8482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return true;
8492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                default:
8502d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    if (statusCode >= 500) {
8513d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                        onRegistrationFailed(response);
8522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        return true;
8532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    }
8542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
8552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
8562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return false;
8572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
8582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
859903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        private boolean handleAuthentication(ResponseEvent event)
860903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                throws SipException {
861903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            Response response = event.getResponse();
862903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            String nonce = getNonceFromResponse(response);
863ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan            if (nonce == null) {
864ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                onError(SipErrorCode.SERVER_ERROR,
865ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                        "server does not provide challenge");
866903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                return false;
867ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan            } else if (mAuthenticationRetryCount < 2) {
868903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                mClientTransaction = mSipHelper.handleChallenge(
869903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                        event, getAccountManager());
870903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                mDialog = mClientTransaction.getDialog();
871ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                mAuthenticationRetryCount++;
872ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                if (isLoggable(this, event)) {
873ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                    Log.d(TAG, "   authentication retry count="
874ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                            + mAuthenticationRetryCount);
875ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                }
876903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                return true;
877ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan            } else {
878a936b256eb1611b5d8b88d0cd61f21225152cc82Hung-ying Tyan                if (crossDomainAuthenticationRequired(response)) {
879a936b256eb1611b5d8b88d0cd61f21225152cc82Hung-ying Tyan                    onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION,
880a936b256eb1611b5d8b88d0cd61f21225152cc82Hung-ying Tyan                            getRealmFromResponse(response));
881a936b256eb1611b5d8b88d0cd61f21225152cc82Hung-ying Tyan                } else {
882a936b256eb1611b5d8b88d0cd61f21225152cc82Hung-ying Tyan                    onError(SipErrorCode.INVALID_CREDENTIALS,
883a936b256eb1611b5d8b88d0cd61f21225152cc82Hung-ying Tyan                            "incorrect username or password");
884a936b256eb1611b5d8b88d0cd61f21225152cc82Hung-ying Tyan                }
885ee8a884f3504c981be8a1d6888b4590a0a394e05Hung-ying Tyan                return false;
886903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            }
887903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        }
888903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan
88900a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan        private boolean crossDomainAuthenticationRequired(Response response) {
89000a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan            String realm = getRealmFromResponse(response);
89100a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan            if (realm == null) realm = "";
89200a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan            return !mLocalProfile.getSipDomain().trim().equals(realm.trim());
89300a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan        }
89400a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan
8952d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private AccountManager getAccountManager() {
8962d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return new AccountManager() {
8972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                public UserCredentials getCredentials(ClientTransaction
8982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        challengedTransaction, String realm) {
8992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return new UserCredentials() {
9002d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        public String getUserName() {
9010f7de88cb9eef781117fa2f2b69ba2698237637eChung-yih Wang                            String username = mLocalProfile.getAuthUserName();
9020f7de88cb9eef781117fa2f2b69ba2698237637eChung-yih Wang                            return (!TextUtils.isEmpty(username) ? username :
9030f7de88cb9eef781117fa2f2b69ba2698237637eChung-yih Wang                                    mLocalProfile.getUserName());
9042d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        }
9052d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
9062d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        public String getPassword() {
9072d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                            return mPassword;
9082d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        }
9092d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
9102d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        public String getSipDomain() {
9112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                            return mLocalProfile.getSipDomain();
9122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        }
9132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    };
9142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
9152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            };
9162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
9172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
91800a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan        private String getRealmFromResponse(Response response) {
91900a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan            WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader(
92000a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan                    SIPHeaderNames.WWW_AUTHENTICATE);
92100a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan            if (wwwAuth != null) return wwwAuth.getRealm();
92200a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan            ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
92300a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan                    SIPHeaderNames.PROXY_AUTHENTICATE);
92400a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan            return (proxyAuth == null) ? null : proxyAuth.getRealm();
92500a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan        }
92600a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan
9272d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private String getNonceFromResponse(Response response) {
9287d137e40cd36290c6bfb5beaf66f4018ae92c97fChung-yih Wang            WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader(
9297d137e40cd36290c6bfb5beaf66f4018ae92c97fChung-yih Wang                    SIPHeaderNames.WWW_AUTHENTICATE);
9307d137e40cd36290c6bfb5beaf66f4018ae92c97fChung-yih Wang            if (wwwAuth != null) return wwwAuth.getNonce();
9317d137e40cd36290c6bfb5beaf66f4018ae92c97fChung-yih Wang            ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
9327d137e40cd36290c6bfb5beaf66f4018ae92c97fChung-yih Wang                    SIPHeaderNames.PROXY_AUTHENTICATE);
9337d137e40cd36290c6bfb5beaf66f4018ae92c97fChung-yih Wang            return (proxyAuth == null) ? null : proxyAuth.getNonce();
9342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
9352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
9362d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private boolean readyForCall(EventObject evt) throws SipException {
9372d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            // expect MakeCallCommand, RegisterCommand, DEREGISTER
9382d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (evt instanceof MakeCallCommand) {
939fb3a98b1d8d0ad040980d509c4c5341928b9460bHung-ying Tyan                mState = SipSession.State.OUTGOING_CALL;
9402d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                MakeCallCommand cmd = (MakeCallCommand) evt;
9412d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mPeerProfile = cmd.getPeerProfile();
9422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
94395b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                        mPeerProfile, cmd.getSessionDescription(),
94495b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                        generateTag());
9452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mDialog = mClientTransaction.getDialog();
9462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                addSipSession(this);
9479352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                startSessionTimer(cmd.getTimeout());
9489ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan                mProxy.onCalling(this);
9492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
9502d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (evt instanceof RegisterCommand) {
951fb3a98b1d8d0ad040980d509c4c5341928b9460bHung-ying Tyan                mState = SipSession.State.REGISTERING;
9522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                int duration = ((RegisterCommand) evt).getDuration();
9532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
9542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        generateTag(), duration);
9552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mDialog = mClientTransaction.getDialog();
9562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                addSipSession(this);
9572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mProxy.onRegistering(this);
9582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
9592d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (DEREGISTER == evt) {
960fb3a98b1d8d0ad040980d509c4c5341928b9460bHung-ying Tyan                mState = SipSession.State.DEREGISTERING;
9612d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
9622d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        generateTag(), 0);
9632d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mDialog = mClientTransaction.getDialog();
9642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                addSipSession(this);
9652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mProxy.onRegistering(this);
9662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
9672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
9682d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return false;
9692d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
9702d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
9712d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private boolean incomingCall(EventObject evt) throws SipException {
9722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            // expect MakeCallCommand(answering) , END_CALL cmd , Cancel
9732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (evt instanceof MakeCallCommand) {
9742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // answer call
975fb3a98b1d8d0ad040980d509c4c5341928b9460bHung-ying Tyan                mState = SipSession.State.INCOMING_CALL_ANSWERING;
9762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
9772d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        mLocalProfile,
9782d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        ((MakeCallCommand) evt).getSessionDescription(),
9792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        mServerTransaction);
9809352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                startSessionTimer(((MakeCallCommand) evt).getTimeout());
9812d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
9822d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (END_CALL == evt) {
9832d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mSipHelper.sendInviteBusyHere(mInviteReceived,
9842d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        mServerTransaction);
9852d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                endCallNormally();
9862d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
9872d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (isRequestEvent(Request.CANCEL, evt)) {
9882d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                RequestEvent event = (RequestEvent) evt;
9892d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mSipHelper.sendResponse(event, Response.OK);
9902d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mSipHelper.sendInviteRequestTerminated(
9912d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        mInviteReceived.getRequest(), mServerTransaction);
9922d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                endCallNormally();
9932d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
9942d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
9952d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return false;
9962d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
9972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
9982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private boolean incomingCallToInCall(EventObject evt)
9992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                throws SipException {
10002d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            // expect ACK, CANCEL request
10012d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (isRequestEvent(Request.ACK, evt)) {
10022d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                establishCall();
10032d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
10042d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (isRequestEvent(Request.CANCEL, evt)) {
10052d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // http://tools.ietf.org/html/rfc3261#section-9.2
10062d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // Final response has been sent; do nothing here.
10072d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
10082d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
10092d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return false;
10102d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
10112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
10122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private boolean outgoingCall(EventObject evt) throws SipException {
10132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (expectResponse(Request.INVITE, evt)) {
10142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                ResponseEvent event = (ResponseEvent) evt;
10152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                Response response = event.getResponse();
10162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
10172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                int statusCode = response.getStatusCode();
10182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                switch (statusCode) {
10192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                case Response.RINGING:
10206057cd00d95c756b78f22c67279cb982bc0674efHung-ying Tyan                case Response.CALL_IS_BEING_FORWARDED:
10216057cd00d95c756b78f22c67279cb982bc0674efHung-ying Tyan                case Response.QUEUED:
10226057cd00d95c756b78f22c67279cb982bc0674efHung-ying Tyan                case Response.SESSION_PROGRESS:
10236057cd00d95c756b78f22c67279cb982bc0674efHung-ying Tyan                    // feedback any provisional responses (except TRYING) as
10246057cd00d95c756b78f22c67279cb982bc0674efHung-ying Tyan                    // ring back for better UX
102584a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                    if (mState == SipSession.State.OUTGOING_CALL) {
102684a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                        mState = SipSession.State.OUTGOING_CALL_RING_BACK;
10279352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                        cancelSessionTimer();
10289ea96c6cade1f25d4d77dcbd24854df431548b36Hung-ying Tyan                        mProxy.onRingingBack(this);
10292d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    }
10302d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return true;
10312d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                case Response.OK:
10322d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    mSipHelper.sendInviteAck(event, mDialog);
103395b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                    mPeerSessionDescription = extractContent(response);
10342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    establishCall();
10352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return true;
10360e0633828928481658c0e09e5893f6214b57ba38Chung-yih Wang                case Response.UNAUTHORIZED:
10372d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                case Response.PROXY_AUTHENTICATION_REQUIRED:
1038a936b256eb1611b5d8b88d0cd61f21225152cc82Hung-ying Tyan                    if (handleAuthentication(event)) {
1039903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                        addSipSession(this);
1040903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    }
10412d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return true;
10422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                case Response.REQUEST_PENDING:
10432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    // TODO:
10442d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    // rfc3261#section-14.1; re-schedule invite
10452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return true;
10462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                default:
10472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    if (statusCode >= 400) {
10482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        // error: an ack is sent automatically by the stack
1049903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                        onError(response);
10502d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        return true;
10512d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    } else if (statusCode >= 300) {
10522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        // TODO: handle 3xx (redirect)
10532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    } else {
10542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        return true;
10552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    }
10562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
10572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return false;
10582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (END_CALL == evt) {
10592d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // RFC says that UA should not send out cancel when no
10602d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // response comes back yet. We are cheating for not checking
10612d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // response.
106284a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                mState = SipSession.State.OUTGOING_CALL_CANCELING;
1063fb3a98b1d8d0ad040980d509c4c5341928b9460bHung-ying Tyan                mSipHelper.sendCancel(mClientTransaction);
10649352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                startSessionTimer(CANCEL_CALL_TIMER);
10652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
10660a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan            } else if (isRequestEvent(Request.INVITE, evt)) {
10670a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan                // Call self? Send BUSY HERE so server may redirect the call to
10680a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan                // voice mailbox.
10690a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan                RequestEvent event = (RequestEvent) evt;
10700a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan                mSipHelper.sendInviteBusyHere(event,
10710a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan                        event.getServerTransaction());
10720a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan                return true;
10732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
10742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return false;
10752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
10762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
10772d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private boolean outgoingCallToReady(EventObject evt)
10782d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                throws SipException {
10792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (evt instanceof ResponseEvent) {
10802d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                ResponseEvent event = (ResponseEvent) evt;
10812d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                Response response = event.getResponse();
10822d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                int statusCode = response.getStatusCode();
10832d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                if (expectResponse(Request.CANCEL, evt)) {
1084025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    if (statusCode == Response.OK) {
1085025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                        // do nothing; wait for REQUEST_TERMINATED
1086025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                        return true;
1087025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                    }
1088025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                } else if (expectResponse(Request.INVITE, evt)) {
10899352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                    switch (statusCode) {
10909352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                        case Response.OK:
1091025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                            outgoingCall(evt); // abort Cancel
10929352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                            return true;
10939352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                        case Response.REQUEST_TERMINATED:
10949352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                            endCallNormally();
10959352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                            return true;
10962d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    }
10972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                } else {
10982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return false;
10992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
11002d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
11012d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                if (statusCode >= 400) {
11023d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    onError(response);
11032d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    return true;
11042d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                }
11052d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (evt instanceof TransactionTerminatedEvent) {
11062d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // rfc3261#section-14.1:
11072d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // if re-invite gets timed out, terminate the dialog; but
11082d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // re-invite is not reliable, just let it go and pretend
11092d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // nothing happened.
11102d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                onError(new SipException("timed out"));
11112d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
11122d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return false;
11132d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
11142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
11152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private boolean inCall(EventObject evt) throws SipException {
11162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            // expect END_CALL cmd, BYE request, hold call (MakeCallCommand)
11172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            // OK retransmission is handled in SipStack
11182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (END_CALL == evt) {
11192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // rfc3261#section-15.1.1
11202d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mSipHelper.sendBye(mDialog);
11212d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                endCallNormally();
11222d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
11232d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (isRequestEvent(Request.INVITE, evt)) {
11242d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // got Re-INVITE
112584a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                mState = SipSession.State.INCOMING_CALL;
1126fb3a98b1d8d0ad040980d509c4c5341928b9460bHung-ying Tyan                RequestEvent event = mInviteReceived = (RequestEvent) evt;
112795b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                mPeerSessionDescription = extractContent(event.getRequest());
11282d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mServerTransaction = null;
11292d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
11302d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
11312d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (isRequestEvent(Request.BYE, evt)) {
11322d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
11332d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                endCallNormally();
11342d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
11352d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            } else if (evt instanceof MakeCallCommand) {
11362d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                // to change call
1137fb3a98b1d8d0ad040980d509c4c5341928b9460bHung-ying Tyan                mState = SipSession.State.OUTGOING_CALL;
11382d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                mClientTransaction = mSipHelper.sendReinvite(mDialog,
11392d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                        ((MakeCallCommand) evt).getSessionDescription());
11409352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                startSessionTimer(((MakeCallCommand) evt).getTimeout());
11412d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return true;
11422d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
11432d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return false;
11442d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
11452d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
11469352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        // timeout in seconds
11479352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        private void startSessionTimer(int timeout) {
11489352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            if (timeout > 0) {
11494a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mSessionTimer = new SessionTimer();
11504a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mSessionTimer.start(timeout);
11519352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            }
11529352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        }
11539352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
11549352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        private void cancelSessionTimer() {
11554a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            if (mSessionTimer != null) {
11564a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mSessionTimer.cancel();
11574a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mSessionTimer = null;
11589352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            }
11599352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        }
11609352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
1161903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        private String createErrorMessage(Response response) {
1162624d5b4e8c20516516d0bff74479b9f5abdfe61cHung-ying Tyan            return String.format("%s (%d)", response.getReasonPhrase(),
1163624d5b4e8c20516516d0bff74479b9f5abdfe61cHung-ying Tyan                    response.getStatusCode());
1164903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        }
1165903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan
11662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private void establishCall() {
116784a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            mState = SipSession.State.IN_CALL;
11682d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mInCall = true;
11699352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            cancelSessionTimer();
11702d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mProxy.onCallEstablished(this, mPeerSessionDescription);
11712d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
11722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
11732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private void endCallNormally() {
11742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            reset();
11752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mProxy.onCallEnded(this);
11762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
11772d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
117897963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan        private void endCallOnError(int errorCode, String message) {
11792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            reset();
118097963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan            mProxy.onError(this, errorCode, message);
1181903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        }
1182903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan
1183903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        private void endCallOnBusy() {
1184903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            reset();
1185903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            mProxy.onCallBusy(this);
11862d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
11872d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
118897963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan        private void onError(int errorCode, String message) {
11899352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            cancelSessionTimer();
11903d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            switch (mState) {
119184a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.REGISTERING:
119284a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.DEREGISTERING:
11933d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    onRegistrationFailed(errorCode, message);
11943d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    break;
11953d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                default:
11964189d99b6e4877352049b7447b7f0734ef99b9e8Hung-ying Tyan                    endCallOnError(errorCode, message);
11972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
11982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
11992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
12003d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan
12013d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan        private void onError(Throwable exception) {
12023d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            exception = getRootCause(exception);
12033d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            onError(getErrorCode(exception), exception.toString());
12043d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan        }
12053d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan
1206903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        private void onError(Response response) {
12073d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            int statusCode = response.getStatusCode();
1208ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan            if (!mInCall && (statusCode == Response.BUSY_HERE)) {
12093d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                endCallOnBusy();
1210903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            } else {
12113d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                onError(getErrorCode(statusCode), createErrorMessage(response));
1212903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            }
1213903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        }
1214903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan
121597963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan        private int getErrorCode(int responseStatusCode) {
1216903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            switch (responseStatusCode) {
1217ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                case Response.TEMPORARILY_UNAVAILABLE:
1218ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                case Response.FORBIDDEN:
1219ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                case Response.GONE:
1220903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                case Response.NOT_FOUND:
1221ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                case Response.NOT_ACCEPTABLE:
1222ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                case Response.NOT_ACCEPTABLE_HERE:
1223ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                    return SipErrorCode.PEER_NOT_REACHABLE;
1224ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan
1225ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                case Response.REQUEST_URI_TOO_LONG:
1226903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                case Response.ADDRESS_INCOMPLETE:
1227ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                case Response.AMBIGUOUS:
1228903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    return SipErrorCode.INVALID_REMOTE_URI;
1229ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan
1230903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                case Response.REQUEST_TIMEOUT:
1231903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    return SipErrorCode.TIME_OUT;
1232ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan
1233903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                default:
1234903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    if (responseStatusCode < 500) {
1235903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                        return SipErrorCode.CLIENT_ERROR;
1236903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    } else {
1237903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                        return SipErrorCode.SERVER_ERROR;
1238903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    }
1239903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            }
1240903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        }
1241903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan
1242903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        private Throwable getRootCause(Throwable exception) {
1243903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            Throwable cause = exception.getCause();
1244903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            while (cause != null) {
1245903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                exception = cause;
1246903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                cause = exception.getCause();
1247903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            }
1248903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            return exception;
1249903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        }
1250903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan
125197963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan        private int getErrorCode(Throwable exception) {
1252903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            String message = exception.getMessage();
1253903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            if (exception instanceof UnknownHostException) {
1254c6548fd9eda7b58f5a2e2a9c01e3c7cafd42fafbHung-ying Tyan                return SipErrorCode.SERVER_UNREACHABLE;
1255903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            } else if (exception instanceof IOException) {
1256903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                return SipErrorCode.SOCKET_ERROR;
1257903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            } else {
1258903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                return SipErrorCode.CLIENT_ERROR;
1259903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            }
1260903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        }
1261903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan
12622d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private void onRegistrationDone(int duration) {
12633d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            reset();
12642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mProxy.onRegistrationDone(this, duration);
12652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
12662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
126797963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan        private void onRegistrationFailed(int errorCode, String message) {
12683d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            reset();
126997963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan            mProxy.onRegistrationFailed(this, errorCode, message);
1270903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        }
1271903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan
12722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private void onRegistrationFailed(Throwable exception) {
1273903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            exception = getRootCause(exception);
1274903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            onRegistrationFailed(getErrorCode(exception),
1275903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    exception.toString());
12762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
12773d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan
12783d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan        private void onRegistrationFailed(Response response) {
12793d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            int statusCode = response.getStatusCode();
12803d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan            onRegistrationFailed(getErrorCode(statusCode),
12813d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    createErrorMessage(response));
12823d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan        }
12834a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
12844a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        // Notes: SipSessionListener will be replaced by the keepalive process
12854a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        // @param interval in seconds
12864a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        public void startKeepAliveProcess(int interval,
12874a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                KeepAliveProcessCallback callback) throws SipException {
12884a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            synchronized (SipSessionGroup.this) {
12894a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                startKeepAliveProcess(interval, mLocalProfile, callback);
12904a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
12914a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        }
12924a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
12934a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        // Notes: SipSessionListener will be replaced by the keepalive process
12944a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        // @param interval in seconds
12954a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        public void startKeepAliveProcess(int interval, SipProfile peerProfile,
12964a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                KeepAliveProcessCallback callback) throws SipException {
12974a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            synchronized (SipSessionGroup.this) {
12984a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                if (mKeepAliveProcess != null) {
12994a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    throw new SipException("Cannot create more than one "
13004a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            + "keepalive process in a SipSession");
13014a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
13024a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mPeerProfile = peerProfile;
13034a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mKeepAliveProcess = new KeepAliveProcess();
13044a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mProxy.setListener(mKeepAliveProcess);
13054a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mKeepAliveProcess.start(interval, callback);
13064a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
13074a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        }
13084a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13094a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        public void stopKeepAliveProcess() {
13104a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            synchronized (SipSessionGroup.this) {
13114a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                if (mKeepAliveProcess != null) {
13124a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    mKeepAliveProcess.stop();
13134a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    mKeepAliveProcess = null;
13144a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
13154a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
13164a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        }
13174a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13184a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        class KeepAliveProcess extends SipSessionAdapter implements Runnable {
13194a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            private static final String TAG = "SipKeepAlive";
13204a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            private boolean mRunning = false;
13214a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            private KeepAliveProcessCallback mCallback;
13224a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13234a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            private boolean mPortChanged = false;
13244a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            private int mRPort = 0;
1325e65f3a896f03bba5327ce4f3989c0422855450caHung-ying Tyan            private int mInterval; // just for debugging
13264a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13274a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // @param interval in seconds
13284a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            void start(int interval, KeepAliveProcessCallback callback) {
13294a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                if (mRunning) return;
13304a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mRunning = true;
1331e65f3a896f03bba5327ce4f3989c0422855450caHung-ying Tyan                mInterval = interval;
13324a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mCallback = new KeepAliveProcessCallbackProxy(callback);
13334a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mWakeupTimer.set(interval * 1000, this);
13344a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                if (DEBUG) {
13354a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    Log.d(TAG, "start keepalive:"
13364a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            + mLocalProfile.getUriString());
13374a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
13384a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13394a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                // No need to run the first time in a separate thread for now
13404a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                run();
13414a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
13424a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13434a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // return true if the event is consumed
13444a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            boolean process(EventObject evt) throws SipException {
13454a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                if (mRunning && (mState == SipSession.State.PINGING)) {
13464a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    if (evt instanceof ResponseEvent) {
13474a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        if (parseOptionsResult(evt)) {
13484a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            if (mPortChanged) {
13494a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                                stop();
13504a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            } else {
13514a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                                cancelSessionTimer();
13524a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                                removeSipSession(SipSessionImpl.this);
13534a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            }
13544a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            mCallback.onResponse(mPortChanged);
13554a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            return true;
13564a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        }
13574a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    }
13584a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
13594a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                return false;
13604a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
13614a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13624a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // SipSessionAdapter
13634a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // To react to the session timeout event and network error.
13644a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            @Override
13654a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            public void onError(ISipSession session, int errorCode, String message) {
13664a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                stop();
13674a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                mCallback.onError(errorCode, message);
13684a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
13694a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13704a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // SipWakeupTimer timeout handler
13714a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // To send out keepalive message.
13724a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            @Override
13734a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            public void run() {
13744a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                synchronized (SipSessionGroup.this) {
13754a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    if (!mRunning) return;
13764a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13774a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    if (DEBUG_PING) {
13784a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        Log.d(TAG, "keepalive: " + mLocalProfile.getUriString()
1379e65f3a896f03bba5327ce4f3989c0422855450caHung-ying Tyan                                + " --> " + mPeerProfile + ", interval=" + mInterval);
13804a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    }
13814a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    try {
13824a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        sendKeepAlive();
13834a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    } catch (Throwable t) {
13844a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        Log.w(TAG, "keepalive error: " + ": "
13854a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                                + mLocalProfile.getUriString(), getRootCause(t));
13864a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        // It's possible that the keepalive process is being stopped
13874a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        // during session.sendKeepAlive() so need to check mRunning
13884a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        // again here.
13894a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        if (mRunning) SipSessionImpl.this.onError(t);
13904a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    }
13914a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
13924a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
13934a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
13944a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            void stop() {
13954a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                synchronized (SipSessionGroup.this) {
13964a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    if (DEBUG) {
13974a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        Log.d(TAG, "stop keepalive:" + mLocalProfile.getUriString()
13984a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                                + ",RPort=" + mRPort);
13994a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    }
14004a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    mRunning = false;
14014a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    mWakeupTimer.cancel(this);
14024a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    reset();
14034a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
14044a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
14054a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
14064a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            private void sendKeepAlive() throws SipException, InterruptedException {
14074a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                synchronized (SipSessionGroup.this) {
14084a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    mState = SipSession.State.PINGING;
14094a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    mClientTransaction = mSipHelper.sendOptions(
14104a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            mLocalProfile, mPeerProfile, generateTag());
14114a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    mDialog = mClientTransaction.getDialog();
14124a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    addSipSession(SipSessionImpl.this);
14134a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
14144a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    startSessionTimer(KEEPALIVE_TIMEOUT);
14154a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    // when timed out, onError() will be called with SipErrorCode.TIME_OUT
14164a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
14174a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
14184a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
14194a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            private boolean parseOptionsResult(EventObject evt) {
14204a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                if (expectResponse(Request.OPTIONS, evt)) {
14214a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    ResponseEvent event = (ResponseEvent) evt;
14224a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    int rPort = getRPortFromResponse(event.getResponse());
14234a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    if (rPort != -1) {
14244a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        if (mRPort == 0) mRPort = rPort;
14254a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        if (mRPort != rPort) {
14264a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            mPortChanged = true;
14274a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            if (DEBUG) Log.d(TAG, String.format(
14284a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                                    "rport is changed: %d <> %d", mRPort, rPort));
14294a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            mRPort = rPort;
14304a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        } else {
14314a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                            if (DEBUG) Log.d(TAG, "rport is the same: " + rPort);
14324a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        }
14334a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    } else {
14344a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        if (DEBUG) Log.w(TAG, "peer did not respond rport");
14354a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    }
14364a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    return true;
14374a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
14384a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                return false;
14394a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
14404a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
14414a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            private int getRPortFromResponse(Response response) {
14424a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                ViaHeader viaHeader = (ViaHeader)(response.getHeader(
14434a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        SIPHeaderNames.VIA));
14444a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                return (viaHeader == null) ? -1 : viaHeader.getRPort();
14454a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
14464a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        }
14472d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
14482d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
14492d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    /**
14502d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     * @return true if the event is a request event matching the specified
14512d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     *      method; false otherwise
14522d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     */
14532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static boolean isRequestEvent(String method, EventObject event) {
14542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        try {
14552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (event instanceof RequestEvent) {
14562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                RequestEvent requestEvent = (RequestEvent) event;
14572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return method.equals(requestEvent.getRequest().getMethod());
14582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
14592d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } catch (Throwable e) {
14602d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
14612d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return false;
14622d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
14632d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
14642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static String getCseqMethod(Message message) {
14652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod();
14662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
14672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
14682d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    /**
14692d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     * @return true if the event is a response event and the CSeqHeader method
14702d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     * match the given arguments; false otherwise
14712d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     */
14722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static boolean expectResponse(
14732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            String expectedMethod, EventObject evt) {
14742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if (evt instanceof ResponseEvent) {
14752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            ResponseEvent event = (ResponseEvent) evt;
14762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            Response response = event.getResponse();
14772d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return expectedMethod.equalsIgnoreCase(getCseqMethod(response));
14782d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
14792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return false;
14802d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
14812d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
14822d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    /**
14832d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     * @return true if the event is a response event and the response code and
14842d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     *      CSeqHeader method match the given arguments; false otherwise
14852d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang     */
14862d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static boolean expectResponse(
14872d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            int responseCode, String expectedMethod, EventObject evt) {
14882d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if (evt instanceof ResponseEvent) {
14892d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            ResponseEvent event = (ResponseEvent) evt;
14902d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            Response response = event.getResponse();
14912d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (response.getStatusCode() == responseCode) {
14922d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                return expectedMethod.equalsIgnoreCase(getCseqMethod(response));
14932d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            }
14942d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
14952d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        return false;
14962d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
14972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
14982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static SipProfile createPeerProfile(Request request)
14992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            throws SipException {
15002d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        try {
15012d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            FromHeader fromHeader =
15022d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang                    (FromHeader) request.getHeader(FromHeader.NAME);
15032d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            Address address = fromHeader.getAddress();
15042d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            SipURI uri = (SipURI) address.getURI();
15052d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            String username = uri.getUser();
15062d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            if (username == null) username = ANONYMOUS;
150758ee2acba8953814cc4bf65d2f28f7dd498b5779Hung-ying Tyan            int port = uri.getPort();
150858ee2acba8953814cc4bf65d2f28f7dd498b5779Hung-ying Tyan            SipProfile.Builder builder =
150958ee2acba8953814cc4bf65d2f28f7dd498b5779Hung-ying Tyan                    new SipProfile.Builder(username, uri.getHost())
151058ee2acba8953814cc4bf65d2f28f7dd498b5779Hung-ying Tyan                    .setDisplayName(address.getDisplayName());
151158ee2acba8953814cc4bf65d2f28f7dd498b5779Hung-ying Tyan            if (port > 0) builder.setPort(port);
151258ee2acba8953814cc4bf65d2f28f7dd498b5779Hung-ying Tyan            return builder.build();
151399bf4e45c4566172189735b34b368b76660ca57aHung-ying Tyan        } catch (IllegalArgumentException e) {
15142d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            throw new SipException("createPeerProfile()", e);
15152d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } catch (ParseException e) {
15162d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            throw new SipException("createPeerProfile()", e);
15172d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
15182d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
15192d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
1520c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan    private static boolean isLoggable(SipSessionImpl s) {
1521c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        if (s != null) {
1522c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            switch (s.mState) {
152384a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan                case SipSession.State.PINGING:
1524c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan                    return DEBUG_PING;
1525c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            }
1526c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        }
1527c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        return DEBUG;
1528c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan    }
1529c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan
15300a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan    private static boolean isLoggable(EventObject evt) {
15310a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan        return isLoggable(null, evt);
15320a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan    }
15330a6e717fb6846f66b8dc853e079f2166307bfc60Hung-ying Tyan
1534c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan    private static boolean isLoggable(SipSessionImpl s, EventObject evt) {
1535c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        if (!isLoggable(s)) return false;
1536c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        if (evt == null) return false;
1537c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan
15384a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        if (evt instanceof ResponseEvent) {
1539c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            Response response = ((ResponseEvent) evt).getResponse();
1540c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) {
1541c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan                return DEBUG_PING;
1542c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            }
1543c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            return DEBUG;
1544c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        } else if (evt instanceof RequestEvent) {
15454a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            if (isRequestEvent(Request.OPTIONS, evt)) {
15464a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                return DEBUG_PING;
15474a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            }
1548c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan            return DEBUG;
1549c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        }
1550c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan        return false;
1551c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan    }
1552c7510581b81d63536db7d46ca8533106c8cf57c6Hung-ying Tyan
15532d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private static String log(EventObject evt) {
15542d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        if (evt instanceof RequestEvent) {
15552d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return ((RequestEvent) evt).getRequest().toString();
15562d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } else if (evt instanceof ResponseEvent) {
15572d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return ((ResponseEvent) evt).getResponse().toString();
15582d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        } else {
15592d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return evt.toString();
15602d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
15612d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
15622d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
15632d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private class RegisterCommand extends EventObject {
15642d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        private int mDuration;
15652d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
15662d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public RegisterCommand(int duration) {
15672d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            super(SipSessionGroup.this);
15682d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mDuration = duration;
15692d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
15702d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
15712d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public int getDuration() {
15722d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return mDuration;
15732d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
15742d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    }
15752d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
15762d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang    private class MakeCallCommand extends EventObject {
157795b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh        private String mSessionDescription;
15789352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        private int mTimeout; // in seconds
15792d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
15802d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public MakeCallCommand(SipProfile peerProfile,
158195b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh                String sessionDescription) {
15829352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            this(peerProfile, sessionDescription, -1);
15839352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        }
15849352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan
15859352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        public MakeCallCommand(SipProfile peerProfile,
15869352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan                String sessionDescription, int timeout) {
15872d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            super(peerProfile);
15882d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            mSessionDescription = sessionDescription;
15899352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            mTimeout = timeout;
15902d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
15912d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
15922d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        public SipProfile getPeerProfile() {
15932d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return (SipProfile) getSource();
15942d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
15952d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
159695b15c35608fe3ea679c8a478c6cbd841623371eChia-chi Yeh        public String getSessionDescription() {
15972d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang            return mSessionDescription;
15982d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang        }
15992d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang
16009352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        public int getTimeout() {
16019352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan            return mTimeout;
16029352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan        }
16039352cf1a4d46492fc48a20f7d825a9bcb6e8b365Hung-ying Tyan    }
16044a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
16054a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    /** Class to help safely run KeepAliveProcessCallback in a different thread. */
16064a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback {
16074a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        private KeepAliveProcessCallback mCallback;
16084a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
16094a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        KeepAliveProcessCallbackProxy(KeepAliveProcessCallback callback) {
16104a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            mCallback = callback;
16114a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        }
16124a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
16134a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        private void proxy(Runnable runnable) {
16144a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // One thread for each calling back.
16154a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // Note: Guarantee ordering if the issue becomes important. Currently,
16164a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            // the chance of handling two callback events at a time is none.
16174a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            new Thread(runnable, "SIP-KeepAliveProcessCallbackThread").start();
16184a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        }
16194a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
16204a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        public void onResponse(final boolean portChanged) {
16214a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            if (mCallback == null) return;
16224a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            proxy(new Runnable() {
16234a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                public void run() {
16244a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    try {
16254a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        mCallback.onResponse(portChanged);
16264a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    } catch (Throwable t) {
16274a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        Log.w(TAG, "onResponse", t);
16284a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    }
16294a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
16304a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            });
16314a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        }
16324a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan
16334a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        public void onError(final int errorCode, final String description) {
16344a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            if (mCallback == null) return;
16354a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            proxy(new Runnable() {
16364a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                public void run() {
16374a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    try {
16384a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        mCallback.onError(errorCode, description);
16394a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    } catch (Throwable t) {
16404a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                        Log.w(TAG, "onError", t);
16414a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                    }
16424a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan                }
16434a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan            });
16444a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan        }
16454a267a9158a62010cd76ab93681586ea8e3d6015Hung-ying Tyan    }
16462d94231ef91c732f649ff7af9520ee9eac441b16Chung-yih Wang}
1647