SipPhone.java revision e86a8d443e921caec2c398ef74f0b6d573a15bb1
10825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/*
20825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Copyright (C) 2010 The Android Open Source Project
30825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
40825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Licensed under the Apache License, Version 2.0 (the "License");
50825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * you may not use this file except in compliance with the License.
60825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * You may obtain a copy of the License at
70825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
80825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *      http://www.apache.org/licenses/LICENSE-2.0
90825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Unless required by applicable law or agreed to in writing, software
110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * distributed under the License is distributed on an "AS IS" BASIS,
120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * See the License for the specific language governing permissions and
140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * limitations under the License.
150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
170825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepackage com.android.internal.telephony.sip;
180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
190825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.content.Context;
200825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.media.AudioManager;
210825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.rtp.AudioGroup;
220825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipAudioCall;
230825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipErrorCode;
240825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipException;
250825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipManager;
260825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipProfile;
270825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipSession;
280825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.os.AsyncResult;
290825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.os.Message;
30b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensenimport android.telephony.DisconnectCause;
310825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.PhoneNumberUtils;
320825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.ServiceState;
330825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.text.TextUtils;
3499c2e1d6749cfad2a8ca94a47857d8c3bfc09454Wink Savilleimport android.telephony.Rlog;
350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
360825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Call;
370825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.CallStateException;
380825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Connection;
390825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Phone;
400825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.PhoneConstants;
410825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.PhoneNotifier;
420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
430825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.text.ParseException;
440825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.util.List;
450825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.util.regex.Pattern;
460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/**
480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * {@hide}
490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
500825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepublic class SipPhone extends SipPhoneBase {
510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final String LOG_TAG = "SipPhone";
52f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static final boolean DBG = true;
53ff4e317d24f0d23bdc0f306d53ddc51f2f1ecf6aWink Saville    private static final boolean VDBG = false; // STOPSHIP if true
540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    // A call that is ringing or (call) waiting
5922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mRingingCall = new SipCall();
6022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mForegroundCall = new SipCall();
6122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mBackgroundCall = new SipCall();
620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipManager mSipManager;
640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipProfile mProfile;
650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
67ff4e317d24f0d23bdc0f306d53ddc51f2f1ecf6aWink Saville        super("SIP:" + profile.getUriString(), context, notifier);
680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
69f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("new SipPhone: " + profile.getUriString());
7022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mRingingCall = new SipCall();
7122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mForegroundCall = new SipCall();
7222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mBackgroundCall = new SipCall();
730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mProfile = profile;
740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mSipManager = SipManager.newInstance(context);
750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(Object o) {
790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (o == this) return true;
800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!(o instanceof SipPhone)) return false;
810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        SipPhone that = (SipPhone) o;
820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString().equals(that.mProfile.getUriString());
830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public String getSipUri() {
860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString();
870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(SipPhone phone) {
900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return getSipUri().equals(phone.getSipUri());
910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
93e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal    public Connection takeIncomingCall(Object incomingCall) {
94f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // FIXME: Is synchronizing on the class necessary, should we use a mLockObj?
95f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // Also there are many things not synchronized, of course
96f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // this may be true of CdmaPhone and GsmPhone too!!!
970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
98f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (!(incomingCall instanceof SipAudioCall)) {
99e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: ret=null, not a SipAudioCall");
100e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
101f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
10222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mRingingCall.getState().isAlive()) {
103e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: ret=null, ringingCall not alive");
104e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
105f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
1060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // FIXME: is it true that we cannot take any incoming call if
1080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // both foreground and background are active
10922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mForegroundCall.getState().isAlive()
11022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    && mBackgroundCall.getState().isAlive()) {
111f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
112e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    log("takeIncomingCall: ret=null," + " foreground and background both alive");
113f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
114e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
1150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
1180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
119e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: taking call from: "
1200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + sipAudioCall.getPeerProfile().getUriString());
1210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String localUri = sipAudioCall.getLocalProfile().getUriString();
1220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (localUri.equals(mProfile.getUriString())) {
12322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    boolean makeCallWait = mForegroundCall.getState().isAlive();
124e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    SipConnection connection = mRingingCall.initIncomingCall(sipAudioCall,
125e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                            makeCallWait);
126e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    if (sipAudioCall.getState() != SipSession.State.INCOMING_CALL) {
1270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        // Peer cancelled the call!
128e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                        if (DBG) log("    takeIncomingCall: call cancelled !!");
12922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        mRingingCall.reset();
130e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                        connection = null;
1310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
132e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    return connection;
1330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
1340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (Exception e) {
1350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Peer may cancel the call at any time during the time we hook
1360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // up ringingCall with sipAudioCall. Clean up ringingCall when
1370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // that happens.
138e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("    takeIncomingCall: exception e=" + e);
13922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.reset();
1400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
141e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            if (DBG) log("takeIncomingCall: NOT taking !!");
142e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            return null;
1430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
146f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void acceptCall() throws CallStateException {
1480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
14922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if ((mRingingCall.getState() == Call.State.INCOMING) ||
15022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    (mRingingCall.getState() == Call.State.WAITING)) {
151f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("acceptCall: accepting");
1520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Always unmute when answering a new call
15322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.setMute(false);
15422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.acceptCall();
1550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
156f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
157f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("acceptCall:" +
158f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        " throw CallStateException(\"phone not ringing\")");
159f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
165f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void rejectCall() throws CallStateException {
1670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
16822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mRingingCall.getState().isRinging()) {
169f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("rejectCall: rejecting");
17022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.rejectCall();
1710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
172f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
173f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("rejectCall:" +
174f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        " throw CallStateException(\"phone not ringing\")");
175f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
181f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1826bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn    public Connection dial(String dialString, int videoState) throws CallStateException {
1830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
1846bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn            return dialInternal(dialString, videoState);
1850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1886bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn    private Connection dialInternal(String dialString, int videoState)
1890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throws CallStateException {
190f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("dialInternal: dialString=" + (VDBG ? dialString : "xxxxxx"));
1910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        clearDisconnected();
1920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!canDial()) {
194f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            throw new CallStateException("dialInternal: cannot dial in current state");
1950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
19622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        if (mForegroundCall.getState() == SipCall.State.ACTIVE) {
1970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switchHoldingAndActive();
1980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
19922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        if (mForegroundCall.getState() != SipCall.State.IDLE) {
2000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            //we should have failed in !canDial() above before we get here
2010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("cannot dial in current state");
2020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
20422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mForegroundCall.setMute(false);
2050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        try {
20622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            Connection c = mForegroundCall.dial(dialString);
2070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return c;
2080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } catch (SipException e) {
209f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("dialInternal: ", e);
2100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("dial error: " + e);
2110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
214f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void switchHoldingAndActive() throws CallStateException {
216f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("dialInternal: switch fg and bg");
2170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
21822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.switchWith(mBackgroundCall);
21922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mBackgroundCall.getState().isAlive()) mBackgroundCall.hold();
22022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mForegroundCall.getState().isAlive()) mForegroundCall.unhold();
2210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
224f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canConference() {
226f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("canConference: ret=true");
2270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return true;
2280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
230f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference() throws CallStateException {
2320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
23322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if ((mForegroundCall.getState() != SipCall.State.ACTIVE)
23422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    || (mForegroundCall.getState() != SipCall.State.ACTIVE)) {
2350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("wrong state to merge calls: fg="
23622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mForegroundCall.getState() + ", bg="
23722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mBackgroundCall.getState());
2380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
239f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (DBG) log("conference: merge fg & bg");
24022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.merge(mBackgroundCall);
2410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference(Call that) throws CallStateException {
2450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
2460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!(that instanceof SipCall)) {
2470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("expect " + SipCall.class
2480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ", cannot merge with " + that.getClass());
2490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
25022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.merge((SipCall) that);
2510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
254f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canTransfer() {
2560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return false;
2570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
259f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
260cbaa45bbf2cab852b6c9c3a887e9f803d4e857eaWink Saville    public void explicitCallTransfer() {
2610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        //mCT.explicitCallTransfer();
2620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
264f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void clearDisconnected() {
2660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
26722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mRingingCall.clearDisconnected();
26822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.clearDisconnected();
26922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mBackgroundCall.clearDisconnected();
2700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            updatePhoneState();
2720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyPreciseCallStateChanged();
2730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
276f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendDtmf(char c) {
2780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
279f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("sendDtmf called with invalid character '" + c + "'");
28022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        } else if (mForegroundCall.getState().isAlive()) {
2810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
28222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mForegroundCall.sendDtmf(c);
2830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
287f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void startDtmf(char c) {
2890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
290f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("startDtmf called with invalid character '" + c + "'");
2910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
2920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sendDtmf(c);
2930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
296f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void stopDtmf() {
2980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // no op
2990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendBurstDtmf(String dtmfString) {
302f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        loge("sendBurstDtmf() is a CDMA method");
3030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
305f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getOutgoingCallerIdDisplay(Message onComplete) {
3070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
3080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
312f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
3140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           Message onComplete) {
3150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what's this for SIP?
3160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
320f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getCallWaiting(Message onComplete) {
3220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
3230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
327f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setCallWaiting(boolean enable, Message onComplete) {
3290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
330f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        loge("call waiting not supported");
3310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
334184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota    public void setEchoSuppressionEnabled() {
335184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota        // Echo suppression may not be available on every device. So, check
336184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota        // whether it is supported
3370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
338184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
339184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            String echoSuppression = audioManager.getParameters("ec_supported");
340184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            if (echoSuppression.contains("off")) {
341184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota                mForegroundCall.setAudioGroupMode();
342184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            }
3430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
346f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setMute(boolean muted) {
3480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
34922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.setMute(muted);
3500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
353f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean getMute() {
35522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return (mForegroundCall.getState().isAlive()
35622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                ? mForegroundCall.getMute()
35722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                : mBackgroundCall.getMute());
3580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
360f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getForegroundCall() {
36222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mForegroundCall;
3630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
365f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getBackgroundCall() {
36722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mBackgroundCall;
3680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
370f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getRingingCall() {
37222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mRingingCall;
3730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
375f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public ServiceState getServiceState() {
3770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: we may need to provide this when data connectivity is lost
3780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // or when server is down
3790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return super.getServiceState();
3800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getUriString(SipProfile p) {
3830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // SipProfile.getUriString() may contain "SIP:" and port
3840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return p.getUserName() + "@" + getSipDomain(p);
3850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getSipDomain(SipProfile p) {
3880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        String domain = p.getSipDomain();
3890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // TODO: move this to SipProfile
3900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (domain.endsWith(":5060")) {
3910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain.substring(0, domain.length() - 5);
3920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
3930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain;
3940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
397f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
398f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
399f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        int sessionState = sipAudioCall.getState();
400f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        switch (sessionState) {
401f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.READY_TO_CALL:            return Call.State.IDLE;
402f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.INCOMING_CALL:
403f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
404f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL:            return Call.State.DIALING;
405f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
406f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
407f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.IN_CALL:                  return Call.State.ACTIVE;
408f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            default:
409f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                slog("illegal connection state: " + sessionState);
410f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return Call.State.DISCONNECTED;
411f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
412f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
413f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
414f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void log(String s) {
415f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.d(LOG_TAG, s);
416f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
417f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
418f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static void slog(String s) {
419f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.d(LOG_TAG, s);
420f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
421f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
422f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void loge(String s) {
423f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.e(LOG_TAG, s);
424f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
425f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
426f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void loge(String s, Exception e) {
427f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.e(LOG_TAG, s, e);
428f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
429f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
4300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipCall extends SipCallBase {
431f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SC_TAG = "SipCall";
432f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SC_DBG = true;
433f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SC_VDBG = false; // STOPSHIP if true
434f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
4350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void reset() {
436f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("reset");
43722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.clear();
4380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.IDLE);
4390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void switchWith(SipCall that) {
442f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("switchWith");
4430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall tmp = new SipCall();
4450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                tmp.takeOver(this);
4460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                this.takeOver(that);
4470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                that.takeOver(tmp);
4480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void takeOver(SipCall that) {
452f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("takeOver");
45322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections = that.mConnections;
45422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mState = that.mState;
45522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
4560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).changeOwner(this);
4570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Phone getPhone() {
4620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return SipPhone.this;
4630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public List<Connection> getConnections() {
467f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_VDBG) log("getConnections");
4680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // FIXME should return Collections.unmodifiableList();
47022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                return mConnections;
4710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        Connection dial(String originalNumber) throws SipException {
475f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("dial: num=" + (SC_VDBG ? originalNumber : "xxx"));
476f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            // TODO: Should this be synchronized?
4770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String calleeSipUri = originalNumber;
4780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!calleeSipUri.contains("@")) {
4790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String replaceStr = Pattern.quote(mProfile.getUserName() + "@");
4800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr,
4810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        calleeSipUri + "@");
4820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
4840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipProfile callee =
4850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        new SipProfile.Builder(calleeSipUri).build();
4860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection c = new SipConnection(this, callee,
4870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        originalNumber);
4880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                c.dial();
48922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mConnections.add(c);
4900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(Call.State.DIALING);
4910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return c;
4920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (ParseException e) {
4930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new SipException("dial", e);
4940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
4990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
50022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                if (mState.isAlive()) {
501f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("hangup: call " + getState()
5020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
5030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(State.DISCONNECTING);
5040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    CallStateException excp = null;
50522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    for (Connection c : mConnections) {
5060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        try {
5070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            c.hangup();
5080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        } catch (CallStateException e) {
5090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            excp = e;
5100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
5110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
5120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (excp != null) throw excp;
5130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } else {
514f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("hangup: dead call " + getState()
5150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
5160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
5170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
520e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal        SipConnection initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
5210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipProfile callee = sipAudioCall.getPeerProfile();
5220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipConnection c = new SipConnection(this, callee);
52322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.add(c);
5240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
5260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            c.initIncomingCall(sipAudioCall, newState);
5270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
5290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyNewRingingConnectionP(c);
530e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            return c;
5310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void rejectCall() throws CallStateException {
534f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("rejectCall:");
5350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            hangup();
5360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
539f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("acceptCall: accepting");
54022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (this != mRingingCall) {
5410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a non-ringing call");
5420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
54322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mConnections.size() != 1) {
5440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a conf call");
5450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
54622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            ((SipConnection) mConnections.get(0)).acceptCall();
5470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean isSpeakerOn() {
550f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Boolean ret = ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
5510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    .isSpeakerphoneOn();
552f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_VDBG) log("isSpeakerOn: ret=" + ret);
553f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            return ret;
5540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setAudioGroupMode() {
5570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
558f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (audioGroup == null) {
559f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("setAudioGroupMode: audioGroup == null ignore");
560f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return;
561f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
5620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int mode = audioGroup.getMode();
56322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState == State.HOLDING) {
5640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
5650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (getMute()) {
5660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_MUTED);
5670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (isSpeakerOn()) {
5680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
5690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
5700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_NORMAL);
5710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
572f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log(String.format(
573f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    "setAudioGroupMode change: %d --> %d", mode,
5740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    audioGroup.getMode()));
5750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
578f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("hold:");
5790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.HOLDING);
58022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) ((SipConnection) c).hold();
5810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
5820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold() throws CallStateException {
585f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("unhold:");
5860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.ACTIVE);
5870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = new AudioGroup();
58822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
5890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).unhold(audioGroup);
5900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
5920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
595f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("setMute: muted=" + muted);
59622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
5970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).setMute(muted);
5980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
60222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            boolean ret = mConnections.isEmpty()
6030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    ? false
60422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    : ((SipConnection) mConnections.get(0)).getMute();
605f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("getMute: ret=" + ret);
606f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            return ret;
6070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void merge(SipCall that) throws CallStateException {
610f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("merge:");
6110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
6120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // copy to an array to avoid concurrent modification as connections
6140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // in that.connections will be removed in add(SipConnection).
61522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            Connection[] cc = that.mConnections.toArray(
61622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    new Connection[that.mConnections.size()]);
6170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : cc) {
6180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection conn = (SipConnection) c;
6190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                add(conn);
6200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (conn.getState() == Call.State.HOLDING) {
6210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    conn.unhold(audioGroup);
6220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            that.setState(Call.State.IDLE);
6250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void add(SipConnection conn) {
628f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("add:");
6290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipCall call = conn.getCall();
6300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (call == this) return;
63122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (call != null) call.mConnections.remove(conn);
6320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
63322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.add(conn);
6340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            conn.changeOwner(this);
6350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void sendDtmf(char c) {
638f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("sendDtmf: c=" + c);
6390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
640f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (audioGroup == null) {
641f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("sendDtmf: audioGroup == null, ignore c=" + c);
642f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return;
643f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
6440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            audioGroup.sendDtmf(convertDtmf(c));
6450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private int convertDtmf(char c) {
6480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int code = c - '0';
6490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((code < 0) || (code > 9)) {
6500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                switch (c) {
6510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '*': return 10;
6520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '#': return 11;
6530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'A': return 12;
6540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'B': return 13;
6550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'C': return 14;
6560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'D': return 15;
6570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    default:
6580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        throw new IllegalArgumentException(
6590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                "invalid DTMF char: " + (int) c);
6600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return code;
6630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
6660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(State newState) {
66722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != newState) {
66822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                if (SC_DBG) log("setState: cur state" + mState
6690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + " --> " + newState + ": " + this + ": on phone "
67022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + getPhone() + " " + mConnections.size());
6710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (newState == Call.State.ALERTING) {
67322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    mState = newState; // need in ALERTING to enable ringback
67422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    startRingbackTone();
67522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                } else if (mState == Call.State.ALERTING) {
67622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    stopRingbackTone();
6770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
67822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mState = newState;
6790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                updatePhoneState();
6800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                notifyPreciseCallStateChanged();
6810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionStateChanged(SipConnection conn) {
6850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // this can be called back when a conf call is formed
686f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("onConnectionStateChanged: conn=" + conn);
68722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != State.ACTIVE) {
6880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(conn.getState());
6890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionEnded(SipConnection conn) {
6930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // set state to DISCONNECTED only when all conns are disconnected
694f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("onConnectionEnded: conn=" + conn);
69522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != State.DISCONNECTED) {
6960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                boolean allConnectionsDisconnected = true;
697f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("---check connections: "
69822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mConnections.size());
69922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                for (Connection c : mConnections) {
700f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("   state=" + c.getState() + ": "
7010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + c);
7020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (c.getState() != State.DISCONNECTED) {
7030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        allConnectionsDisconnected = false;
7040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        break;
7050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (allConnectionsDisconnected) setState(State.DISCONNECTED);
7080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyDisconnectP(conn);
7100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private AudioGroup getAudioGroup() {
71322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mConnections.isEmpty()) return null;
71422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            return ((SipConnection) mConnections.get(0)).getAudioGroup();
7150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
716f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
717f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
718f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SC_TAG, s);
719f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
7200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
7210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipConnection extends SipConnectionBase {
723f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SCN_TAG = "SipConnection";
724f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SCN_DBG = true;
725f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
7260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipCall mOwner;
7270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCall mSipAudioCall;
7280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private Call.State mState = Call.State.IDLE;
7290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipProfile mPeer;
7300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean mIncoming = false;
731f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private String mOriginalNumber; // may be a PSTN number
7320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
7340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
735b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            protected void onCallEnded(int cause) {
7360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (getDisconnectCause() != DisconnectCause.LOCAL) {
7370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setDisconnectCause(cause);
7380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
7400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(Call.State.DISCONNECTED);
7410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
742f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    // FIXME: This goes null and is synchronized, but many uses aren't sync'd
7430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mSipAudioCall = null;
7440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    String sessionState = (sipAudioCall == null)
7450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? ""
7460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : (sipAudioCall.getState() + ", ");
747f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SCN_DBG) log("[SipAudioCallAdapter] onCallEnded: "
7480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + mPeer.getUriString() + ": " + sessionState
7490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + "cause: " + getDisconnectCause() + ", on phone "
7500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + getPhone());
7510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
7520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
7530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.close();
7540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionEnded(SipConnection.this);
7560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallEstablished(SipAudioCall call) {
7610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
762f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                // Race onChanged synchronized this isn't
7630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.ACTIVE) call.startAudio();
7640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallHeld(SipAudioCall call) {
7680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
769f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                // Race onChanged synchronized this isn't
7700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.HOLDING) call.startAudio();
7710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onChanged(SipAudioCall call) {
7750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
7760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    Call.State newState = getCallStateFrom(call);
7770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (mState == newState) return;
7780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (newState == Call.State.INCOMING) {
7790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(mOwner.getState()); // INCOMING or WAITING
7800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    } else {
78122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        if (mOwner == mRingingCall) {
78222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                            if (mRingingCall.getState() == Call.State.WAITING) {
7830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                try {
7840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    switchHoldingAndActive();
7850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                } catch (CallStateException e) {
7860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    // disconnect the call.
7870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    onCallEnded(DisconnectCause.LOCAL);
7880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    return;
7890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                }
7900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            }
79122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                            mForegroundCall.switchWith(mRingingCall);
7920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
7930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(newState);
7940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionStateChanged(SipConnection.this);
796f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SCN_DBG) log("onChanged: "
7970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + mPeer.getUriString() + ": " + mState
7980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + " on phone " + getPhone());
7990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
8000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
803b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            protected void onError(int cause) {
804f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("onError: " + cause);
8050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onCallEnded(cause);
8060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        };
8080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee,
8100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String originalNumber) {
8110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super(originalNumber);
8120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
8130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mPeer = callee;
8140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOriginalNumber = originalNumber;
8150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee) {
8180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            this(owner, callee, getUriString(callee));
8190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getCnapName() {
8230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String displayName = mPeer.getDisplayName();
8240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return TextUtils.isEmpty(displayName) ? null
8250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                                  : displayName;
8260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public int getNumberPresentation() {
8300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return PhoneConstants.PRESENTATION_ALLOWED;
8310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
8340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
8350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = sipAudioCall;
8360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sipAudioCall.setListener(mAdapter); // call back to set state
8370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mIncoming = true;
8380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
8410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
8430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall(): " + e);
8450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void changeOwner(SipCall owner) {
8490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
8500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AudioGroup getAudioGroup() {
8530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (mSipAudioCall == null) return null;
8540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mSipAudioCall.getAudioGroup();
8550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void dial() throws SipException {
8580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.DIALING);
8590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
8600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    TIMEOUT_MAKE_CALL);
8610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setListener(mAdapter);
8620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
8650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.HOLDING);
8660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
8680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("hold(): " + e);
8700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold(AudioGroup audioGroup) throws CallStateException {
8740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setAudioGroup(audioGroup);
8750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.ACTIVE);
8760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
8780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("unhold(): " + e);
8800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
8840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) {
885f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("setState: prev muted=" + !muted + " new muted=" + muted);
8860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.toggleMute();
8870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
8910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return (mSipAudioCall == null) ? false
8920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           : mSipAudioCall.isMuted();
8930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(Call.State state) {
8970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state == mState) return;
8980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super.setState(state);
8990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mState = state;
9000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Call.State getState() {
9040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mState;
9050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public boolean isIncoming() {
9090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mIncoming;
9100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getAddress() {
9140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // Phone app uses this to query caller ID. Return the original dial
9150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // number (which may be a PSTN number) instead of the peer's SIP
9160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // URI.
9170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOriginalNumber;
9180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipCall getCall() {
9220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner;
9230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected Phone getPhone() {
9270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner.getPhone();
9280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
9320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
933f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("hangup: conn=" + mPeer.getUriString()
9340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ": " + mState + ": on phone "
9350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + getPhone().getPhoneName());
9360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (!mState.isAlive()) return;
9370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                try {
9380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
9390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
9400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
9410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.endCall();
9420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
9430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } catch (SipException e) {
9440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException("hangup(): " + e);
9450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } finally {
9460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mAdapter.onCallEnded(((mState == Call.State.INCOMING)
9470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            || (mState == Call.State.WAITING))
9480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? DisconnectCause.INCOMING_REJECTED
9490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : DisconnectCause.LOCAL);
9500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
9510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void separate() throws CallStateException {
9560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
9570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall call = (getPhone() == SipPhone.this)
95822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        ? (SipCall) getBackgroundCall()
95922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        : (SipCall) getForegroundCall();
9600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (call.getState() != Call.State.IDLE) {
9610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException(
9620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            "cannot put conn back to a call in non-idle state: "
9630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + call.getState());
9640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
965f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("separate: conn="
9660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + mPeer.getUriString() + " from " + mOwner + " back to "
9670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + call);
9680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // separate the AudioGroup and connection from the original call
9700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                Phone originalPhone = getPhone();
9710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                AudioGroup audioGroup = call.getAudioGroup(); // may be null
9720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.add(this);
9730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.setAudioGroup(audioGroup);
9740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // put the original call to bg; and the separated call becomes
9760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // fg if it was in bg
9770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                originalPhone.switchHoldingAndActive();
9780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // start audio and notify the phone app of the state change
98022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                call = (SipCall) getForegroundCall();
9810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.startAudio();
9820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.onConnectionStateChanged(this);
9830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
986f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
987f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SCN_TAG, s);
9880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
9900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
992f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SACA_TAG = "SipAudioCallAdapter";
993f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SACA_DBG = true;
994b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        /** Call ended with cause defined in {@link DisconnectCause}. */
995b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        protected abstract void onCallEnded(int cause);
996b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        /** Call failed with cause defined in {@link DisconnectCause}. */
997b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        protected abstract void onError(int cause);
9980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallEnded(SipAudioCall call) {
1001f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) log("onCallEnded: call=" + call);
10020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            onCallEnded(call.isInCall()
1003b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    ? DisconnectCause.NORMAL
1004b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    : DisconnectCause.INCOMING_MISSED);
10050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallBusy(SipAudioCall call) {
1009f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) log("onCallBusy: call=" + call);
1010b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            onCallEnded(DisconnectCause.BUSY);
10110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onError(SipAudioCall call, int errorCode,
10150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String errorMessage) {
1016f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) {
1017f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                log("onError: call=" + call + " code="+ SipErrorCode.toString(errorCode)
1018f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    + ": " + errorMessage);
1019f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
10200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switch (errorCode) {
10210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_UNREACHABLE:
1022b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.SERVER_UNREACHABLE);
10230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.PEER_NOT_REACHABLE:
1025b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.NUMBER_UNREACHABLE);
10260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_REMOTE_URI:
1028b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.INVALID_NUMBER);
10290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TIME_OUT:
10310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TRANSACTION_TERMINTED:
1032b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.TIMED_OUT);
10330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.DATA_CONNECTION_LOST:
1035b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.LOST_SIGNAL);
10360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_CREDENTIALS:
1038b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.INVALID_CREDENTIALS);
10390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION:
1041b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.OUT_OF_NETWORK);
10420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_ERROR:
1044b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.SERVER_ERROR);
10450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SOCKET_ERROR:
10470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CLIENT_ERROR:
10480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                default:
1049b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.ERROR_UNSPECIFIED);
10500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
10510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1052f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
1053f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
1054f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SACA_TAG, s);
1055f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
10560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
10570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville}
1058