10825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/*
20825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Copyright (C) 2010 The Android Open Source Project
30825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
40825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Licensed under the Apache License, Version 2.0 (the "License");
50825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * you may not use this file except in compliance with the License.
60825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * You may obtain a copy of the License at
70825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
80825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *      http://www.apache.org/licenses/LICENSE-2.0
90825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Unless required by applicable law or agreed to in writing, software
110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * distributed under the License is distributed on an "AS IS" BASIS,
120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * See the License for the specific language governing permissions and
140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * limitations under the License.
150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
170825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepackage com.android.internal.telephony.sip;
180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
190825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.content.Context;
200825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.media.AudioManager;
210825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.rtp.AudioGroup;
220825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipAudioCall;
230825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipErrorCode;
240825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipException;
250825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipManager;
260825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipProfile;
270825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipSession;
280825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.os.AsyncResult;
290825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.os.Message;
300825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.PhoneNumberUtils;
310825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.ServiceState;
320825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.text.TextUtils;
330825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.util.Log;
340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
350825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Call;
360825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.CallStateException;
370825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Connection;
380825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Phone;
390825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.PhoneConstants;
400825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.PhoneNotifier;
410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
420825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.text.ParseException;
430825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.util.List;
440825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.util.regex.Pattern;
450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/**
470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * {@hide}
480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
490825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepublic class SipPhone extends SipPhoneBase {
500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final String LOG_TAG = "SipPhone";
510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final boolean DEBUG = true;
520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    // A call that is ringing or (call) waiting
570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipCall ringingCall = new SipCall();
580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipCall foregroundCall = new SipCall();
590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipCall backgroundCall = new SipCall();
600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipManager mSipManager;
620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipProfile mProfile;
630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        super(context, notifier);
660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (DEBUG) Log.d(LOG_TAG, "new SipPhone: " + profile.getUriString());
680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        ringingCall = new SipCall();
690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        foregroundCall = new SipCall();
700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        backgroundCall = new SipCall();
710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mProfile = profile;
720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mSipManager = SipManager.newInstance(context);
730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(Object o) {
770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (o == this) return true;
780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!(o instanceof SipPhone)) return false;
790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        SipPhone that = (SipPhone) o;
800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString().equals(that.mProfile.getUriString());
810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public String getPhoneName() {
840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return "SIP:" + getUriString(mProfile);
850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public String getSipUri() {
880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString();
890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(SipPhone phone) {
920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return getSipUri().equals(phone.getSipUri());
930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canTake(Object incomingCall) {
960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!(incomingCall instanceof SipAudioCall)) return false;
980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (ringingCall.getState().isAlive()) return false;
990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // FIXME: is it true that we cannot take any incoming call if
1010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // both foreground and background are active
1020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (foregroundCall.getState().isAlive()
1030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    && backgroundCall.getState().isAlive()) {
1040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return false;
1050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
1080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
1090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (DEBUG) Log.d(LOG_TAG, "+++ taking call from: "
1100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + sipAudioCall.getPeerProfile().getUriString());
1110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String localUri = sipAudioCall.getLocalProfile().getUriString();
1120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (localUri.equals(mProfile.getUriString())) {
1130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    boolean makeCallWait = foregroundCall.getState().isAlive();
1140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
1150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall.getState()
1160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            != SipSession.State.INCOMING_CALL) {
1170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        // Peer cancelled the call!
1180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        if (DEBUG) Log.d(LOG_TAG, "    call cancelled !!");
1190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        ringingCall.reset();
1200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
1210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    return true;
1220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
1230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (Exception e) {
1240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Peer may cancel the call at any time during the time we hook
1250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // up ringingCall with sipAudioCall. Clean up ringingCall when
1260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // that happens.
1270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ringingCall.reset();
1280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return false;
1300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void acceptCall() throws CallStateException {
1340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
1350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((ringingCall.getState() == Call.State.INCOMING) ||
1360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    (ringingCall.getState() == Call.State.WAITING)) {
1370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (DEBUG) Log.d(LOG_TAG, "acceptCall");
1380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Always unmute when answering a new call
1390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ringingCall.setMute(false);
1400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ringingCall.acceptCall();
1410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
1420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void rejectCall() throws CallStateException {
1480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
1490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (ringingCall.getState().isRinging()) {
1500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (DEBUG) Log.d(LOG_TAG, "rejectCall");
1510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ringingCall.rejectCall();
1520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
1530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Connection dial(String dialString) throws CallStateException {
1590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
1600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return dialInternal(dialString);
1610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private Connection dialInternal(String dialString)
1650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throws CallStateException {
1660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        clearDisconnected();
1670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!canDial()) {
1690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("cannot dial in current state");
1700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (foregroundCall.getState() == SipCall.State.ACTIVE) {
1720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switchHoldingAndActive();
1730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (foregroundCall.getState() != SipCall.State.IDLE) {
1750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            //we should have failed in !canDial() above before we get here
1760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("cannot dial in current state");
1770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        foregroundCall.setMute(false);
1800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        try {
1810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Connection c = foregroundCall.dial(dialString);
1820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return c;
1830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } catch (SipException e) {
1840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Log.e(LOG_TAG, "dial()", e);
1850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("dial error: " + e);
1860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void switchHoldingAndActive() throws CallStateException {
1900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (DEBUG) Log.d(LOG_TAG, " ~~~~~~  switch fg and bg");
1910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
1920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            foregroundCall.switchWith(backgroundCall);
1930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (backgroundCall.getState().isAlive()) backgroundCall.hold();
1940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (foregroundCall.getState().isAlive()) foregroundCall.unhold();
1950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canConference() {
1990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return true;
2000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference() throws CallStateException {
2030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
2040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((foregroundCall.getState() != SipCall.State.ACTIVE)
2050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    || (foregroundCall.getState() != SipCall.State.ACTIVE)) {
2060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("wrong state to merge calls: fg="
2070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + foregroundCall.getState() + ", bg="
2080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + backgroundCall.getState());
2090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            foregroundCall.merge(backgroundCall);
2110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference(Call that) throws CallStateException {
2150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
2160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!(that instanceof SipCall)) {
2170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("expect " + SipCall.class
2180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ", cannot merge with " + that.getClass());
2190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            foregroundCall.merge((SipCall) that);
2210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canTransfer() {
2250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return false;
2260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void explicitCallTransfer() throws CallStateException {
2290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        //mCT.explicitCallTransfer();
2300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void clearDisconnected() {
2330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
2340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            ringingCall.clearDisconnected();
2350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            foregroundCall.clearDisconnected();
2360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            backgroundCall.clearDisconnected();
2370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            updatePhoneState();
2390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyPreciseCallStateChanged();
2400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendDtmf(char c) {
2440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
2450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Log.e(LOG_TAG,
2460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    "sendDtmf called with invalid character '" + c + "'");
2470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else if (foregroundCall.getState().isAlive()) {
2480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
2490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                foregroundCall.sendDtmf(c);
2500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void startDtmf(char c) {
2550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
2560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Log.e(LOG_TAG,
2570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                "startDtmf called with invalid character '" + c + "'");
2580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
2590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sendDtmf(c);
2600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void stopDtmf() {
2640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // no op
2650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendBurstDtmf(String dtmfString) {
2680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        Log.e(LOG_TAG, "[SipPhone] sendBurstDtmf() is a CDMA method");
2690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getOutgoingCallerIdDisplay(Message onComplete) {
2720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
2730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
2740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
2750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
2780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           Message onComplete) {
2790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what's this for SIP?
2800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
2810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
2820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getCallWaiting(Message onComplete) {
2850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
2860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
2870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
2880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setCallWaiting(boolean enable, Message onComplete) {
2910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
2920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        Log.e(LOG_TAG, "call waiting not supported");
2930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
2960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setEchoSuppressionEnabled(boolean enabled) {
2970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // TODO: Remove the enabled argument. We should check the speakerphone
2980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // state with AudioManager instead of keeping a state here so the
2990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // method with a state argument is redundant. Also rename the method
3000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // to something like onSpeaerphoneStateChanged(). Echo suppression may
3010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // not be available on every device.
3020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
3030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            foregroundCall.setAudioGroupMode();
3040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setMute(boolean muted) {
3080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
3090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            foregroundCall.setMute(muted);
3100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean getMute() {
3140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return (foregroundCall.getState().isAlive()
3150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ? foregroundCall.getMute()
3160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                : backgroundCall.getMute());
3170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getForegroundCall() {
3200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return foregroundCall;
3210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getBackgroundCall() {
3240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return backgroundCall;
3250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getRingingCall() {
3280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return ringingCall;
3290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public ServiceState getServiceState() {
3320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: we may need to provide this when data connectivity is lost
3330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // or when server is down
3340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return super.getServiceState();
3350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getUriString(SipProfile p) {
3380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // SipProfile.getUriString() may contain "SIP:" and port
3390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return p.getUserName() + "@" + getSipDomain(p);
3400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getSipDomain(SipProfile p) {
3430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        String domain = p.getSipDomain();
3440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // TODO: move this to SipProfile
3450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (domain.endsWith(":5060")) {
3460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain.substring(0, domain.length() - 5);
3470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
3480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain;
3490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipCall extends SipCallBase {
3530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void reset() {
3540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            connections.clear();
3550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.IDLE);
3560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void switchWith(SipCall that) {
3590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
3600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall tmp = new SipCall();
3610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                tmp.takeOver(this);
3620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                this.takeOver(that);
3630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                that.takeOver(tmp);
3640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
3650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void takeOver(SipCall that) {
3680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            connections = that.connections;
3690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            state = that.state;
3700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : connections) {
3710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).changeOwner(this);
3720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
3730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
3760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Phone getPhone() {
3770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return SipPhone.this;
3780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
3810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public List<Connection> getConnections() {
3820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
3830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // FIXME should return Collections.unmodifiableList();
3840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return connections;
3850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
3860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        Connection dial(String originalNumber) throws SipException {
3890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String calleeSipUri = originalNumber;
3900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!calleeSipUri.contains("@")) {
3910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String replaceStr = Pattern.quote(mProfile.getUserName() + "@");
3920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr,
3930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        calleeSipUri + "@");
3940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
3950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
3960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipProfile callee =
3970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        new SipProfile.Builder(calleeSipUri).build();
3980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection c = new SipConnection(this, callee,
3990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        originalNumber);
4000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                c.dial();
4010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                connections.add(c);
4020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(Call.State.DIALING);
4030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return c;
4040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (ParseException e) {
4050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new SipException("dial", e);
4060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
4110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (state.isAlive()) {
4130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (DEBUG) Log.d(LOG_TAG, "hang up call: " + getState()
4140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
4150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(State.DISCONNECTING);
4160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    CallStateException excp = null;
4170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    for (Connection c : connections) {
4180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        try {
4190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            c.hangup();
4200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        } catch (CallStateException e) {
4210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            excp = e;
4220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
4230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
4240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (excp != null) throw excp;
4250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } else {
4260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (DEBUG) Log.d(LOG_TAG, "hang up dead call: " + getState()
4270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
4280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
4290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
4330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipProfile callee = sipAudioCall.getPeerProfile();
4340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipConnection c = new SipConnection(this, callee);
4350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            connections.add(c);
4360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
4380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            c.initIncomingCall(sipAudioCall, newState);
4390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
4410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyNewRingingConnectionP(c);
4420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void rejectCall() throws CallStateException {
4450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            hangup();
4460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
4490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (this != ringingCall) {
4500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a non-ringing call");
4510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (connections.size() != 1) {
4530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a conf call");
4540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            ((SipConnection) connections.get(0)).acceptCall();
4560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean isSpeakerOn() {
4590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
4600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    .isSpeakerphoneOn();
4610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setAudioGroupMode() {
4640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
4650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (audioGroup == null) return;
4660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int mode = audioGroup.getMode();
4670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state == State.HOLDING) {
4680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
4690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (getMute()) {
4700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_MUTED);
4710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (isSpeakerOn()) {
4720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
4730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
4740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_NORMAL);
4750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (DEBUG) Log.d(LOG_TAG, String.format(
4770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    "audioGroup mode change: %d --> %d", mode,
4780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    audioGroup.getMode()));
4790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
4820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.HOLDING);
4830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : connections) ((SipConnection) c).hold();
4840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
4850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold() throws CallStateException {
4880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.ACTIVE);
4890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = new AudioGroup();
4900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : connections) {
4910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).unhold(audioGroup);
4920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
4940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
4970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : connections) {
4980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).setMute(muted);
4990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
5030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return connections.isEmpty()
5040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    ? false
5050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    : ((SipConnection) connections.get(0)).getMute();
5060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void merge(SipCall that) throws CallStateException {
5090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
5100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // copy to an array to avoid concurrent modification as connections
5120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // in that.connections will be removed in add(SipConnection).
5130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Connection[] cc = that.connections.toArray(
5140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    new Connection[that.connections.size()]);
5150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : cc) {
5160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection conn = (SipConnection) c;
5170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                add(conn);
5180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (conn.getState() == Call.State.HOLDING) {
5190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    conn.unhold(audioGroup);
5200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
5210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            that.setState(Call.State.IDLE);
5230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void add(SipConnection conn) {
5260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipCall call = conn.getCall();
5270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (call == this) return;
5280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (call != null) call.connections.remove(conn);
5290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            connections.add(conn);
5310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            conn.changeOwner(this);
5320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void sendDtmf(char c) {
5350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
5360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (audioGroup == null) return;
5370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            audioGroup.sendDtmf(convertDtmf(c));
5380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private int convertDtmf(char c) {
5410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int code = c - '0';
5420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((code < 0) || (code > 9)) {
5430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                switch (c) {
5440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '*': return 10;
5450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '#': return 11;
5460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'A': return 12;
5470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'B': return 13;
5480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'C': return 14;
5490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'D': return 15;
5500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    default:
5510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        throw new IllegalArgumentException(
5520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                "invalid DTMF char: " + (int) c);
5530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
5540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return code;
5560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
5590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(State newState) {
5600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state != newState) {
5610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (DEBUG) Log.v(LOG_TAG, "+***+ call state changed: " + state
5620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + " --> " + newState + ": " + this + ": on phone "
5630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + getPhone() + " " + connections.size());
5640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (newState == Call.State.ALERTING) {
5660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    state = newState; // need in ALERTING to enable ringback
5670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipPhone.this.startRingbackTone();
5680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } else if (state == Call.State.ALERTING) {
5690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipPhone.this.stopRingbackTone();
5700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
5710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                state = newState;
5720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                updatePhoneState();
5730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                notifyPreciseCallStateChanged();
5740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionStateChanged(SipConnection conn) {
5780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // this can be called back when a conf call is formed
5790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state != State.ACTIVE) {
5800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(conn.getState());
5810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionEnded(SipConnection conn) {
5850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // set state to DISCONNECTED only when all conns are disconnected
5860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state != State.DISCONNECTED) {
5870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                boolean allConnectionsDisconnected = true;
5880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (DEBUG) Log.d(LOG_TAG, "---check connections: "
5890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + connections.size());
5900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                for (Connection c : connections) {
5910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (DEBUG) Log.d(LOG_TAG, "   state=" + c.getState() + ": "
5920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + c);
5930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (c.getState() != State.DISCONNECTED) {
5940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        allConnectionsDisconnected = false;
5950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        break;
5960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
5970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
5980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (allConnectionsDisconnected) setState(State.DISCONNECTED);
5990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyDisconnectP(conn);
6010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private AudioGroup getAudioGroup() {
6040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (connections.isEmpty()) return null;
6050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return ((SipConnection) connections.get(0)).getAudioGroup();
6060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
6080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipConnection extends SipConnectionBase {
6100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipCall mOwner;
6110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCall mSipAudioCall;
6120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private Call.State mState = Call.State.IDLE;
6130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipProfile mPeer;
6140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private String mOriginalNumber; // may be a PSTN number
6150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean mIncoming = false;
6160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
6180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
6190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            protected void onCallEnded(DisconnectCause cause) {
6200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (getDisconnectCause() != DisconnectCause.LOCAL) {
6210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setDisconnectCause(cause);
6220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
6240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(Call.State.DISCONNECTED);
6250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
6260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mSipAudioCall = null;
6270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    String sessionState = (sipAudioCall == null)
6280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? ""
6290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : (sipAudioCall.getState() + ", ");
6300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (DEBUG) Log.d(LOG_TAG, "--- connection ended: "
6310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + mPeer.getUriString() + ": " + sessionState
6320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + "cause: " + getDisconnectCause() + ", on phone "
6330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + getPhone());
6340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
6350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
6360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.close();
6370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
6380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionEnded(SipConnection.this);
6390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
6430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallEstablished(SipAudioCall call) {
6440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
6450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.ACTIVE) call.startAudio();
6460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
6490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallHeld(SipAudioCall call) {
6500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
6510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.HOLDING) call.startAudio();
6520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
6550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onChanged(SipAudioCall call) {
6560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
6570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    Call.State newState = getCallStateFrom(call);
6580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (mState == newState) return;
6590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (newState == Call.State.INCOMING) {
6600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(mOwner.getState()); // INCOMING or WAITING
6610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    } else {
6620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        if (mOwner == ringingCall) {
6630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            if (ringingCall.getState() == Call.State.WAITING) {
6640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                try {
6650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    switchHoldingAndActive();
6660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                } catch (CallStateException e) {
6670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    // disconnect the call.
6680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    onCallEnded(DisconnectCause.LOCAL);
6690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    return;
6700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                }
6710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            }
6720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            foregroundCall.switchWith(ringingCall);
6730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
6740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(newState);
6750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
6760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionStateChanged(SipConnection.this);
6770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (DEBUG) Log.v(LOG_TAG, "+***+ connection state changed: "
6780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + mPeer.getUriString() + ": " + mState
6790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + " on phone " + getPhone());
6800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
6840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            protected void onError(DisconnectCause cause) {
6850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (DEBUG) Log.d(LOG_TAG, "SIP error: " + cause);
6860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onCallEnded(cause);
6870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        };
6890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee,
6910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String originalNumber) {
6920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super(originalNumber);
6930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
6940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mPeer = callee;
6950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOriginalNumber = originalNumber;
6960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee) {
6990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            this(owner, callee, getUriString(callee));
7000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
7030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getCnapName() {
7040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String displayName = mPeer.getDisplayName();
7050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return TextUtils.isEmpty(displayName) ? null
7060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                                  : displayName;
7070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
7100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public int getNumberPresentation() {
7110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return PhoneConstants.PRESENTATION_ALLOWED;
7120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
7150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
7160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = sipAudioCall;
7170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sipAudioCall.setListener(mAdapter); // call back to set state
7180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mIncoming = true;
7190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
7220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
7230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
7240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
7250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall(): " + e);
7260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void changeOwner(SipCall owner) {
7300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
7310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AudioGroup getAudioGroup() {
7340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (mSipAudioCall == null) return null;
7350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mSipAudioCall.getAudioGroup();
7360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void dial() throws SipException {
7390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.DIALING);
7400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
7410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    TIMEOUT_MAKE_CALL);
7420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setListener(mAdapter);
7430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
7460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.HOLDING);
7470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
7480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
7490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
7500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("hold(): " + e);
7510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold(AudioGroup audioGroup) throws CallStateException {
7550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setAudioGroup(audioGroup);
7560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.ACTIVE);
7570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
7580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
7590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
7600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("unhold(): " + e);
7610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
7650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) {
7660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.toggleMute();
7670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
7710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return (mSipAudioCall == null) ? false
7720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           : mSipAudioCall.isMuted();
7730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
7760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(Call.State state) {
7770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state == mState) return;
7780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super.setState(state);
7790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mState = state;
7800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
7830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Call.State getState() {
7840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mState;
7850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
7880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public boolean isIncoming() {
7890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mIncoming;
7900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
7930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getAddress() {
7940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // Phone app uses this to query caller ID. Return the original dial
7950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // number (which may be a PSTN number) instead of the peer's SIP
7960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // URI.
7970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOriginalNumber;
7980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipCall getCall() {
8020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner;
8030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected Phone getPhone() {
8070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner.getPhone();
8080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
8120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
8130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (DEBUG) Log.d(LOG_TAG, "hangup conn: " + mPeer.getUriString()
8140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ": " + mState + ": on phone "
8150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + getPhone().getPhoneName());
8160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (!mState.isAlive()) return;
8170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                try {
8180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
8190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
8200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
8210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.endCall();
8220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
8230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } catch (SipException e) {
8240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException("hangup(): " + e);
8250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } finally {
8260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mAdapter.onCallEnded(((mState == Call.State.INCOMING)
8270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            || (mState == Call.State.WAITING))
8280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? DisconnectCause.INCOMING_REJECTED
8290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : DisconnectCause.LOCAL);
8300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
8310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void separate() throws CallStateException {
8360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
8370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall call = (getPhone() == SipPhone.this)
8380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        ? (SipCall) SipPhone.this.getBackgroundCall()
8390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        : (SipCall) SipPhone.this.getForegroundCall();
8400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (call.getState() != Call.State.IDLE) {
8410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException(
8420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            "cannot put conn back to a call in non-idle state: "
8430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + call.getState());
8440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
8450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (DEBUG) Log.d(LOG_TAG, "separate conn: "
8460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + mPeer.getUriString() + " from " + mOwner + " back to "
8470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + call);
8480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // separate the AudioGroup and connection from the original call
8500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                Phone originalPhone = getPhone();
8510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                AudioGroup audioGroup = call.getAudioGroup(); // may be null
8520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.add(this);
8530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.setAudioGroup(audioGroup);
8540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // put the original call to bg; and the separated call becomes
8560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // fg if it was in bg
8570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                originalPhone.switchHoldingAndActive();
8580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // start audio and notify the phone app of the state change
8600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call = (SipCall) SipPhone.this.getForegroundCall();
8610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.startAudio();
8620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.onConnectionStateChanged(this);
8630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
8670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
8690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
8700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        int sessionState = sipAudioCall.getState();
8710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        switch (sessionState) {
8720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SipSession.State.READY_TO_CALL:            return Call.State.IDLE;
8730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SipSession.State.INCOMING_CALL:
8740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SipSession.State.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
8750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SipSession.State.OUTGOING_CALL:            return Call.State.DIALING;
8760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SipSession.State.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
8770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SipSession.State.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
8780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SipSession.State.IN_CALL:                  return Call.State.ACTIVE;
8790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            default:
8800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                Log.w(LOG_TAG, "illegal connection state: " + sessionState);
8810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return Call.State.DISCONNECTED;
8820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
8840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
8860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected abstract void onCallEnded(Connection.DisconnectCause cause);
8870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected abstract void onError(Connection.DisconnectCause cause);
8880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallEnded(SipAudioCall call) {
8910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            onCallEnded(call.isInCall()
8920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    ? Connection.DisconnectCause.NORMAL
8930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    : Connection.DisconnectCause.INCOMING_MISSED);
8940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallBusy(SipAudioCall call) {
8980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            onCallEnded(Connection.DisconnectCause.BUSY);
8990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onError(SipAudioCall call, int errorCode,
9030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String errorMessage) {
9040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switch (errorCode) {
9050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_UNREACHABLE:
9060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.SERVER_UNREACHABLE);
9070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
9080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.PEER_NOT_REACHABLE:
9090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.NUMBER_UNREACHABLE);
9100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
9110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_REMOTE_URI:
9120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.INVALID_NUMBER);
9130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
9140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TIME_OUT:
9150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TRANSACTION_TERMINTED:
9160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.TIMED_OUT);
9170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
9180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.DATA_CONNECTION_LOST:
9190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.LOST_SIGNAL);
9200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
9210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_CREDENTIALS:
9220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.INVALID_CREDENTIALS);
9230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
9240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION:
9250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.OUT_OF_NETWORK);
9260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
9270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_ERROR:
9280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.SERVER_ERROR);
9290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
9300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SOCKET_ERROR:
9310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CLIENT_ERROR:
9320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                default:
9330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    Log.w(LOG_TAG, "error: " + SipErrorCode.toString(errorCode)
9340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + errorMessage);
9350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.ERROR_UNSPECIFIED);
9360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
9390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville}
940