SipPhone.java revision df0280231c51a24a0b66c24034827d7f73d6e1ac
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;
29df0280231c51a24a0b66c24034827d7f73d6e1acSantos Cordonimport android.os.Bundle;
300825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.os.Message;
31b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensenimport android.telephony.DisconnectCause;
320825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.PhoneNumberUtils;
330825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.ServiceState;
340825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.text.TextUtils;
3599c2e1d6749cfad2a8ca94a47857d8c3bfc09454Wink Savilleimport android.telephony.Rlog;
360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
370825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Call;
380825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.CallStateException;
390825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Connection;
400825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Phone;
410825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.PhoneConstants;
420825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.PhoneNotifier;
430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
440825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.text.ParseException;
450825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.util.List;
460825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.util.regex.Pattern;
470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/**
490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * {@hide}
500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
510825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepublic class SipPhone extends SipPhoneBase {
520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final String LOG_TAG = "SipPhone";
53f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static final boolean DBG = true;
54ff4e317d24f0d23bdc0f306d53ddc51f2f1ecf6aWink Saville    private static final boolean VDBG = false; // STOPSHIP if true
550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    // A call that is ringing or (call) waiting
6022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mRingingCall = new SipCall();
6122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mForegroundCall = new SipCall();
6222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mBackgroundCall = new SipCall();
630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipManager mSipManager;
650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipProfile mProfile;
660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
68ff4e317d24f0d23bdc0f306d53ddc51f2f1ecf6aWink Saville        super("SIP:" + profile.getUriString(), context, notifier);
690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
70f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("new SipPhone: " + profile.getUriString());
7122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mRingingCall = new SipCall();
7222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mForegroundCall = new SipCall();
7322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mBackgroundCall = new SipCall();
740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mProfile = profile;
750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mSipManager = SipManager.newInstance(context);
760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(Object o) {
800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (o == this) return true;
810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!(o instanceof SipPhone)) return false;
820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        SipPhone that = (SipPhone) o;
830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString().equals(that.mProfile.getUriString());
840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public String getSipUri() {
870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString();
880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(SipPhone phone) {
910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return getSipUri().equals(phone.getSipUri());
920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
94e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal    public Connection takeIncomingCall(Object incomingCall) {
95f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // FIXME: Is synchronizing on the class necessary, should we use a mLockObj?
96f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // Also there are many things not synchronized, of course
97f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // this may be true of CdmaPhone and GsmPhone too!!!
980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
99f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (!(incomingCall instanceof SipAudioCall)) {
100e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: ret=null, not a SipAudioCall");
101e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
102f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
10322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mRingingCall.getState().isAlive()) {
104e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: ret=null, ringingCall not alive");
105e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
106f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
1070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // FIXME: is it true that we cannot take any incoming call if
1090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // both foreground and background are active
11022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mForegroundCall.getState().isAlive()
11122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    && mBackgroundCall.getState().isAlive()) {
112f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
113e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    log("takeIncomingCall: ret=null," + " foreground and background both alive");
114f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
115e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
1160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
1190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
120e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: taking call from: "
1210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + sipAudioCall.getPeerProfile().getUriString());
1220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String localUri = sipAudioCall.getLocalProfile().getUriString();
1230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (localUri.equals(mProfile.getUriString())) {
12422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    boolean makeCallWait = mForegroundCall.getState().isAlive();
125e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    SipConnection connection = mRingingCall.initIncomingCall(sipAudioCall,
126e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                            makeCallWait);
127e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    if (sipAudioCall.getState() != SipSession.State.INCOMING_CALL) {
1280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        // Peer cancelled the call!
129e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                        if (DBG) log("    takeIncomingCall: call cancelled !!");
13022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        mRingingCall.reset();
131e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                        connection = null;
1320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
133e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    return connection;
1340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
1350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (Exception e) {
1360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Peer may cancel the call at any time during the time we hook
1370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // up ringingCall with sipAudioCall. Clean up ringingCall when
1380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // that happens.
139e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("    takeIncomingCall: exception e=" + e);
14022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.reset();
1410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
142e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            if (DBG) log("takeIncomingCall: NOT taking !!");
143e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            return null;
1440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
147f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1486d05f561549a66b597a5119665ccc3bf8a962d16Andrew Lee    public void acceptCall(int videoState) throws CallStateException {
1490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
15022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if ((mRingingCall.getState() == Call.State.INCOMING) ||
15122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    (mRingingCall.getState() == Call.State.WAITING)) {
152f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("acceptCall: accepting");
1530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Always unmute when answering a new call
15422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.setMute(false);
15522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.acceptCall();
1560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
157f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
158f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("acceptCall:" +
159f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        " throw CallStateException(\"phone not ringing\")");
160f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
166f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void rejectCall() throws CallStateException {
1680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
16922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mRingingCall.getState().isRinging()) {
170f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("rejectCall: rejecting");
17122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.rejectCall();
1720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
173f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
174f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("rejectCall:" +
175f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        " throw CallStateException(\"phone not ringing\")");
176f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
182f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1836bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn    public Connection dial(String dialString, int videoState) throws CallStateException {
1840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
1856bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn            return dialInternal(dialString, videoState);
1860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1896bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn    private Connection dialInternal(String dialString, int videoState)
1900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throws CallStateException {
191f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("dialInternal: dialString=" + (VDBG ? dialString : "xxxxxx"));
1920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        clearDisconnected();
1930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!canDial()) {
195f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            throw new CallStateException("dialInternal: cannot dial in current state");
1960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
19722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        if (mForegroundCall.getState() == SipCall.State.ACTIVE) {
1980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switchHoldingAndActive();
1990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
20022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        if (mForegroundCall.getState() != SipCall.State.IDLE) {
2010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            //we should have failed in !canDial() above before we get here
2020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("cannot dial in current state");
2030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
20522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mForegroundCall.setMute(false);
2060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        try {
20722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            Connection c = mForegroundCall.dial(dialString);
2080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return c;
2090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } catch (SipException e) {
210f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("dialInternal: ", e);
2110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("dial error: " + e);
2120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
215f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void switchHoldingAndActive() throws CallStateException {
217df0280231c51a24a0b66c24034827d7f73d6e1acSantos Cordon        if (DBG) log("switchHoldingAndActive: switch fg and bg");
2180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
21922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.switchWith(mBackgroundCall);
22022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mBackgroundCall.getState().isAlive()) mBackgroundCall.hold();
22122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mForegroundCall.getState().isAlive()) mForegroundCall.unhold();
2220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
225f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canConference() {
227f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("canConference: ret=true");
2280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return true;
2290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
231f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference() throws CallStateException {
2330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
23422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if ((mForegroundCall.getState() != SipCall.State.ACTIVE)
23522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    || (mForegroundCall.getState() != SipCall.State.ACTIVE)) {
2360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("wrong state to merge calls: fg="
23722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mForegroundCall.getState() + ", bg="
23822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mBackgroundCall.getState());
2390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
240f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (DBG) log("conference: merge fg & bg");
24122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.merge(mBackgroundCall);
2420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference(Call that) throws CallStateException {
2460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
2470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!(that instanceof SipCall)) {
2480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("expect " + SipCall.class
2490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ", cannot merge with " + that.getClass());
2500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
25122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.merge((SipCall) that);
2520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
255f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canTransfer() {
2570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return false;
2580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
260f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
261cbaa45bbf2cab852b6c9c3a887e9f803d4e857eaWink Saville    public void explicitCallTransfer() {
2620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        //mCT.explicitCallTransfer();
2630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
265f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void clearDisconnected() {
2670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
26822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mRingingCall.clearDisconnected();
26922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.clearDisconnected();
27022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mBackgroundCall.clearDisconnected();
2710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            updatePhoneState();
2730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyPreciseCallStateChanged();
2740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
277f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendDtmf(char c) {
2790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
280f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("sendDtmf called with invalid character '" + c + "'");
28122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        } else if (mForegroundCall.getState().isAlive()) {
2820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
28322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mForegroundCall.sendDtmf(c);
2840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
288f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void startDtmf(char c) {
2900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
291f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("startDtmf called with invalid character '" + c + "'");
2920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
2930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sendDtmf(c);
2940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
297f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void stopDtmf() {
2990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // no op
3000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendBurstDtmf(String dtmfString) {
303f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        loge("sendBurstDtmf() is a CDMA method");
3040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
306f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getOutgoingCallerIdDisplay(Message onComplete) {
3080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
3090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
313f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
3150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           Message onComplete) {
3160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what's this for SIP?
3170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
321f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getCallWaiting(Message onComplete) {
3230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
3240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
328f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setCallWaiting(boolean enable, Message onComplete) {
3300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
331f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        loge("call waiting not supported");
3320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
335184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota    public void setEchoSuppressionEnabled() {
336184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota        // Echo suppression may not be available on every device. So, check
337184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota        // whether it is supported
3380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
339184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
340184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            String echoSuppression = audioManager.getParameters("ec_supported");
341184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            if (echoSuppression.contains("off")) {
342184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota                mForegroundCall.setAudioGroupMode();
343184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            }
3440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
347f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setMute(boolean muted) {
3490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
35022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.setMute(muted);
3510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
354f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean getMute() {
35622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return (mForegroundCall.getState().isAlive()
35722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                ? mForegroundCall.getMute()
35822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                : mBackgroundCall.getMute());
3590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
361f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getForegroundCall() {
36322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mForegroundCall;
3640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
366f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getBackgroundCall() {
36822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mBackgroundCall;
3690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
371f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getRingingCall() {
37322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mRingingCall;
3740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
376f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public ServiceState getServiceState() {
3780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: we may need to provide this when data connectivity is lost
3790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // or when server is down
3800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return super.getServiceState();
3810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getUriString(SipProfile p) {
3840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // SipProfile.getUriString() may contain "SIP:" and port
3850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return p.getUserName() + "@" + getSipDomain(p);
3860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getSipDomain(SipProfile p) {
3890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        String domain = p.getSipDomain();
3900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // TODO: move this to SipProfile
3910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (domain.endsWith(":5060")) {
3920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain.substring(0, domain.length() - 5);
3930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
3940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain;
3950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
398f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
399f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
400f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        int sessionState = sipAudioCall.getState();
401f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        switch (sessionState) {
402f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.READY_TO_CALL:            return Call.State.IDLE;
403f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.INCOMING_CALL:
404f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
405f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL:            return Call.State.DIALING;
406f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
407f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
408f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.IN_CALL:                  return Call.State.ACTIVE;
409f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            default:
410f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                slog("illegal connection state: " + sessionState);
411f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return Call.State.DISCONNECTED;
412f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
413f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
414f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
415f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void log(String s) {
416f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.d(LOG_TAG, s);
417f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
418f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
419f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static void slog(String s) {
420f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.d(LOG_TAG, s);
421f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
422f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
423f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void loge(String s) {
424f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.e(LOG_TAG, s);
425f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
426f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
427f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void loge(String s, Exception e) {
428f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.e(LOG_TAG, s, e);
429f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
430f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
4310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipCall extends SipCallBase {
432f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SC_TAG = "SipCall";
433f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SC_DBG = true;
434f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SC_VDBG = false; // STOPSHIP if true
435f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
4360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void reset() {
437f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("reset");
43822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.clear();
4390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.IDLE);
4400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void switchWith(SipCall that) {
443f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("switchWith");
4440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall tmp = new SipCall();
4460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                tmp.takeOver(this);
4470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                this.takeOver(that);
4480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                that.takeOver(tmp);
4490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void takeOver(SipCall that) {
453f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("takeOver");
45422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections = that.mConnections;
45522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mState = that.mState;
45622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
4570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).changeOwner(this);
4580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Phone getPhone() {
4630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return SipPhone.this;
4640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public List<Connection> getConnections() {
468f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_VDBG) log("getConnections");
4690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // FIXME should return Collections.unmodifiableList();
47122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                return mConnections;
4720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        Connection dial(String originalNumber) throws SipException {
476f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("dial: num=" + (SC_VDBG ? originalNumber : "xxx"));
477f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            // TODO: Should this be synchronized?
4780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String calleeSipUri = originalNumber;
4790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!calleeSipUri.contains("@")) {
4800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String replaceStr = Pattern.quote(mProfile.getUserName() + "@");
4810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr,
4820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        calleeSipUri + "@");
4830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
4850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipProfile callee =
4860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        new SipProfile.Builder(calleeSipUri).build();
4870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection c = new SipConnection(this, callee,
4880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        originalNumber);
4890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                c.dial();
49022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mConnections.add(c);
4910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(Call.State.DIALING);
4920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return c;
4930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (ParseException e) {
4940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new SipException("dial", e);
4950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
5000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
50122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                if (mState.isAlive()) {
502f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("hangup: call " + getState()
5030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
5040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(State.DISCONNECTING);
5050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    CallStateException excp = null;
50622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    for (Connection c : mConnections) {
5070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        try {
5080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            c.hangup();
5090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        } catch (CallStateException e) {
5100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            excp = e;
5110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
5120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
5130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (excp != null) throw excp;
5140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } else {
515f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("hangup: dead call " + getState()
5160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
5170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
5180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
521e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal        SipConnection initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
5220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipProfile callee = sipAudioCall.getPeerProfile();
5230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipConnection c = new SipConnection(this, callee);
52422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.add(c);
5250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
5270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            c.initIncomingCall(sipAudioCall, newState);
5280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
5300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyNewRingingConnectionP(c);
531e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            return c;
5320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void rejectCall() throws CallStateException {
535f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("rejectCall:");
5360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            hangup();
5370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
540f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("acceptCall: accepting");
54122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (this != mRingingCall) {
5420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a non-ringing call");
5430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
54422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mConnections.size() != 1) {
5450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a conf call");
5460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
54722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            ((SipConnection) mConnections.get(0)).acceptCall();
5480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean isSpeakerOn() {
551f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Boolean ret = ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
5520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    .isSpeakerphoneOn();
553f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_VDBG) log("isSpeakerOn: ret=" + ret);
554f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            return ret;
5550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setAudioGroupMode() {
5580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
559f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (audioGroup == null) {
560f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("setAudioGroupMode: audioGroup == null ignore");
561f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return;
562f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
5630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int mode = audioGroup.getMode();
56422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState == State.HOLDING) {
5650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
5660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (getMute()) {
5670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_MUTED);
5680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (isSpeakerOn()) {
5690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
5700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
5710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_NORMAL);
5720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
573f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log(String.format(
574f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    "setAudioGroupMode change: %d --> %d", mode,
5750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    audioGroup.getMode()));
5760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
579f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("hold:");
5800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.HOLDING);
58122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) ((SipConnection) c).hold();
5820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
5830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold() throws CallStateException {
586f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("unhold:");
5870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.ACTIVE);
5880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = new AudioGroup();
58922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
5900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).unhold(audioGroup);
5910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
5930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
596f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("setMute: muted=" + muted);
59722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
5980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).setMute(muted);
5990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
60322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            boolean ret = mConnections.isEmpty()
6040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    ? false
60522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    : ((SipConnection) mConnections.get(0)).getMute();
606f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("getMute: ret=" + ret);
607f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            return ret;
6080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void merge(SipCall that) throws CallStateException {
611f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("merge:");
6120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
6130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // copy to an array to avoid concurrent modification as connections
6150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // in that.connections will be removed in add(SipConnection).
61622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            Connection[] cc = that.mConnections.toArray(
61722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    new Connection[that.mConnections.size()]);
6180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : cc) {
6190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection conn = (SipConnection) c;
6200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                add(conn);
6210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (conn.getState() == Call.State.HOLDING) {
6220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    conn.unhold(audioGroup);
6230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            that.setState(Call.State.IDLE);
6260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void add(SipConnection conn) {
629f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("add:");
6300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipCall call = conn.getCall();
6310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (call == this) return;
63222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (call != null) call.mConnections.remove(conn);
6330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
63422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.add(conn);
6350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            conn.changeOwner(this);
6360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void sendDtmf(char c) {
639f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("sendDtmf: c=" + c);
6400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
641f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (audioGroup == null) {
642f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("sendDtmf: audioGroup == null, ignore c=" + c);
643f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return;
644f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
6450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            audioGroup.sendDtmf(convertDtmf(c));
6460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private int convertDtmf(char c) {
6490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int code = c - '0';
6500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((code < 0) || (code > 9)) {
6510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                switch (c) {
6520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '*': return 10;
6530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '#': return 11;
6540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'A': return 12;
6550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'B': return 13;
6560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'C': return 14;
6570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'D': return 15;
6580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    default:
6590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        throw new IllegalArgumentException(
6600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                "invalid DTMF char: " + (int) c);
6610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return code;
6640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
6670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(State newState) {
66822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != newState) {
66922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                if (SC_DBG) log("setState: cur state" + mState
6700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + " --> " + newState + ": " + this + ": on phone "
67122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + getPhone() + " " + mConnections.size());
6720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (newState == Call.State.ALERTING) {
67422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    mState = newState; // need in ALERTING to enable ringback
67522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    startRingbackTone();
67622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                } else if (mState == Call.State.ALERTING) {
67722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    stopRingbackTone();
6780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
67922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mState = newState;
6800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                updatePhoneState();
6810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                notifyPreciseCallStateChanged();
6820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionStateChanged(SipConnection conn) {
6860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // this can be called back when a conf call is formed
687f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("onConnectionStateChanged: conn=" + conn);
68822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != State.ACTIVE) {
6890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(conn.getState());
6900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionEnded(SipConnection conn) {
6940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // set state to DISCONNECTED only when all conns are disconnected
695f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("onConnectionEnded: conn=" + conn);
69622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != State.DISCONNECTED) {
6970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                boolean allConnectionsDisconnected = true;
698f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("---check connections: "
69922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mConnections.size());
70022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                for (Connection c : mConnections) {
701f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("   state=" + c.getState() + ": "
7020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + c);
7030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (c.getState() != State.DISCONNECTED) {
7040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        allConnectionsDisconnected = false;
7050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        break;
7060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (allConnectionsDisconnected) setState(State.DISCONNECTED);
7090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyDisconnectP(conn);
7110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private AudioGroup getAudioGroup() {
71422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mConnections.isEmpty()) return null;
71522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            return ((SipConnection) mConnections.get(0)).getAudioGroup();
7160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
717f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
718f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
719f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SC_TAG, s);
720f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
7210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
7220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipConnection extends SipConnectionBase {
724f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SCN_TAG = "SipConnection";
725f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SCN_DBG = true;
726f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
7270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipCall mOwner;
7280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCall mSipAudioCall;
7290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private Call.State mState = Call.State.IDLE;
7300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipProfile mPeer;
7310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean mIncoming = false;
732f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private String mOriginalNumber; // may be a PSTN number
7330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
7350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
736b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            protected void onCallEnded(int cause) {
7370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (getDisconnectCause() != DisconnectCause.LOCAL) {
7380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setDisconnectCause(cause);
7390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
7410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(Call.State.DISCONNECTED);
7420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
743f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    // FIXME: This goes null and is synchronized, but many uses aren't sync'd
7440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mSipAudioCall = null;
7450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    String sessionState = (sipAudioCall == null)
7460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? ""
7470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : (sipAudioCall.getState() + ", ");
748f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SCN_DBG) log("[SipAudioCallAdapter] onCallEnded: "
7490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + mPeer.getUriString() + ": " + sessionState
7500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + "cause: " + getDisconnectCause() + ", on phone "
7510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + getPhone());
7520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
7530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
7540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.close();
7550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionEnded(SipConnection.this);
7570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallEstablished(SipAudioCall call) {
7620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
763f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                // Race onChanged synchronized this isn't
7640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.ACTIVE) call.startAudio();
7650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallHeld(SipAudioCall call) {
7690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
770f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                // Race onChanged synchronized this isn't
7710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.HOLDING) call.startAudio();
7720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onChanged(SipAudioCall call) {
7760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
7770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    Call.State newState = getCallStateFrom(call);
7780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (mState == newState) return;
7790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (newState == Call.State.INCOMING) {
7800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(mOwner.getState()); // INCOMING or WAITING
7810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    } else {
78222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        if (mOwner == mRingingCall) {
78322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                            if (mRingingCall.getState() == Call.State.WAITING) {
7840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                try {
7850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    switchHoldingAndActive();
7860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                } catch (CallStateException e) {
7870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    // disconnect the call.
7880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    onCallEnded(DisconnectCause.LOCAL);
7890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    return;
7900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                }
7910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            }
79222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                            mForegroundCall.switchWith(mRingingCall);
7930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
7940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(newState);
7950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionStateChanged(SipConnection.this);
797f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SCN_DBG) log("onChanged: "
7980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + mPeer.getUriString() + ": " + mState
7990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + " on phone " + getPhone());
8000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
8010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
804b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            protected void onError(int cause) {
805f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("onError: " + cause);
8060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onCallEnded(cause);
8070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        };
8090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee,
8110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String originalNumber) {
8120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super(originalNumber);
8130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
8140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mPeer = callee;
8150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOriginalNumber = originalNumber;
8160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee) {
8190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            this(owner, callee, getUriString(callee));
8200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getCnapName() {
8240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String displayName = mPeer.getDisplayName();
8250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return TextUtils.isEmpty(displayName) ? null
8260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                                  : displayName;
8270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public int getNumberPresentation() {
8310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return PhoneConstants.PRESENTATION_ALLOWED;
8320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
8350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
8360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = sipAudioCall;
8370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sipAudioCall.setListener(mAdapter); // call back to set state
8380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mIncoming = true;
8390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
8420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
8440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall(): " + e);
8460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void changeOwner(SipCall owner) {
8500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
8510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AudioGroup getAudioGroup() {
8540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (mSipAudioCall == null) return null;
8550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mSipAudioCall.getAudioGroup();
8560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void dial() throws SipException {
8590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.DIALING);
8600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
8610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    TIMEOUT_MAKE_CALL);
8620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setListener(mAdapter);
8630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
8660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.HOLDING);
8670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
8690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("hold(): " + e);
8710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold(AudioGroup audioGroup) throws CallStateException {
8750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setAudioGroup(audioGroup);
8760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.ACTIVE);
8770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
8790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("unhold(): " + e);
8810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
8850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) {
886f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("setState: prev muted=" + !muted + " new muted=" + muted);
8870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.toggleMute();
8880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
8920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return (mSipAudioCall == null) ? false
8930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           : mSipAudioCall.isMuted();
8940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(Call.State state) {
8980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state == mState) return;
8990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super.setState(state);
9000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mState = state;
9010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Call.State getState() {
9050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mState;
9060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public boolean isIncoming() {
9100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mIncoming;
9110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getAddress() {
9150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // Phone app uses this to query caller ID. Return the original dial
9160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // number (which may be a PSTN number) instead of the peer's SIP
9170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // URI.
9180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOriginalNumber;
9190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipCall getCall() {
9230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner;
9240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected Phone getPhone() {
9280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner.getPhone();
9290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
9330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
934f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("hangup: conn=" + mPeer.getUriString()
9350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ": " + mState + ": on phone "
9360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + getPhone().getPhoneName());
9370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (!mState.isAlive()) return;
9380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                try {
9390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
9400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
9410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
9420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.endCall();
9430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
9440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } catch (SipException e) {
9450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException("hangup(): " + e);
9460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } finally {
9470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mAdapter.onCallEnded(((mState == Call.State.INCOMING)
9480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            || (mState == Call.State.WAITING))
9490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? DisconnectCause.INCOMING_REJECTED
9500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : DisconnectCause.LOCAL);
9510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
9520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void separate() throws CallStateException {
9570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
9580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall call = (getPhone() == SipPhone.this)
95922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        ? (SipCall) getBackgroundCall()
96022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        : (SipCall) getForegroundCall();
9610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (call.getState() != Call.State.IDLE) {
9620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException(
9630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            "cannot put conn back to a call in non-idle state: "
9640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + call.getState());
9650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
966f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("separate: conn="
9670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + mPeer.getUriString() + " from " + mOwner + " back to "
9680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + call);
9690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // separate the AudioGroup and connection from the original call
9710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                Phone originalPhone = getPhone();
9720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                AudioGroup audioGroup = call.getAudioGroup(); // may be null
9730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.add(this);
9740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.setAudioGroup(audioGroup);
9750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // put the original call to bg; and the separated call becomes
9770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // fg if it was in bg
9780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                originalPhone.switchHoldingAndActive();
9790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // start audio and notify the phone app of the state change
98122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                call = (SipCall) getForegroundCall();
9820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.startAudio();
9830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.onConnectionStateChanged(this);
9840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
987f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
988f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SCN_TAG, s);
9890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
9910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
993f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SACA_TAG = "SipAudioCallAdapter";
994f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SACA_DBG = true;
995b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        /** Call ended with cause defined in {@link DisconnectCause}. */
996b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        protected abstract void onCallEnded(int cause);
997b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        /** Call failed with cause defined in {@link DisconnectCause}. */
998b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        protected abstract void onError(int cause);
9990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallEnded(SipAudioCall call) {
1002f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) log("onCallEnded: call=" + call);
10030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            onCallEnded(call.isInCall()
1004b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    ? DisconnectCause.NORMAL
1005b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    : DisconnectCause.INCOMING_MISSED);
10060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallBusy(SipAudioCall call) {
1010f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) log("onCallBusy: call=" + call);
1011b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            onCallEnded(DisconnectCause.BUSY);
10120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onError(SipAudioCall call, int errorCode,
10160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String errorMessage) {
1017f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) {
1018f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                log("onError: call=" + call + " code="+ SipErrorCode.toString(errorCode)
1019f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    + ": " + errorMessage);
1020f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
10210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switch (errorCode) {
10220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_UNREACHABLE:
1023b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.SERVER_UNREACHABLE);
10240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.PEER_NOT_REACHABLE:
1026b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.NUMBER_UNREACHABLE);
10270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_REMOTE_URI:
1029b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.INVALID_NUMBER);
10300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TIME_OUT:
10320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TRANSACTION_TERMINTED:
1033b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.TIMED_OUT);
10340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.DATA_CONNECTION_LOST:
1036b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.LOST_SIGNAL);
10370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_CREDENTIALS:
1039b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.INVALID_CREDENTIALS);
10400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION:
1042b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.OUT_OF_NETWORK);
10430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_ERROR:
1045b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.SERVER_ERROR);
10460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SOCKET_ERROR:
10480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CLIENT_ERROR:
10490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                default:
1050b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.ERROR_UNSPECIFIED);
10510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
10520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1053f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
1054f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
1055f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SACA_TAG, s);
1056f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
10570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
10580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville}
1059