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;
3399c2e1d6749cfad2a8ca94a47857d8c3bfc09454Wink Savilleimport android.telephony.Rlog;
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";
51f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static final boolean DBG = true;
52ff4e317d24f0d23bdc0f306d53ddc51f2f1ecf6aWink Saville    private static final boolean VDBG = false; // STOPSHIP if true
530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    // A call that is ringing or (call) waiting
5822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mRingingCall = new SipCall();
5922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mForegroundCall = new SipCall();
6022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mBackgroundCall = new SipCall();
610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipManager mSipManager;
630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipProfile mProfile;
640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
66ff4e317d24f0d23bdc0f306d53ddc51f2f1ecf6aWink Saville        super("SIP:" + profile.getUriString(), context, notifier);
670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
68f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("new SipPhone: " + profile.getUriString());
6922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mRingingCall = new SipCall();
7022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mForegroundCall = new SipCall();
7122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mBackgroundCall = new SipCall();
720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mProfile = profile;
730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mSipManager = SipManager.newInstance(context);
740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(Object o) {
780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (o == this) return true;
790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!(o instanceof SipPhone)) return false;
800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        SipPhone that = (SipPhone) o;
810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString().equals(that.mProfile.getUriString());
820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public String getSipUri() {
850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString();
860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(SipPhone phone) {
890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return getSipUri().equals(phone.getSipUri());
900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canTake(Object incomingCall) {
93f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // FIXME: Is synchronizing on the class necessary, should we use a mLockObj?
94f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // Also there are many things not synchronized, of course
95f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // this may be true of CdmaPhone and GsmPhone too!!!
960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
97f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (!(incomingCall instanceof SipAudioCall)) {
98f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("canTake: ret=false, not a SipAudioCall");
99f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return false;
100f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
10122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mRingingCall.getState().isAlive()) {
102f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("canTake: ret=false, ringingCall not alive");
103f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return false;
104f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
1050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // FIXME: is it true that we cannot take any incoming call if
1070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // both foreground and background are active
10822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mForegroundCall.getState().isAlive()
10922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    && mBackgroundCall.getState().isAlive()) {
110f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
111f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("canTake: ret=false," +
112f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                            " foreground and background both alive");
113f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return false;
1150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
1180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
119f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("canTake: taking call from: "
1200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + sipAudioCall.getPeerProfile().getUriString());
1210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String localUri = sipAudioCall.getLocalProfile().getUriString();
1220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (localUri.equals(mProfile.getUriString())) {
12322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    boolean makeCallWait = mForegroundCall.getState().isAlive();
12422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    mRingingCall.initIncomingCall(sipAudioCall, makeCallWait);
1250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall.getState()
1260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            != SipSession.State.INCOMING_CALL) {
1270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        // Peer cancelled the call!
128f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        if (DBG) log("    canTake: call cancelled !!");
12922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        mRingingCall.reset();
1300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
1310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    return true;
1320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
1330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (Exception e) {
1340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Peer may cancel the call at any time during the time we hook
1350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // up ringingCall with sipAudioCall. Clean up ringingCall when
1360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // that happens.
137f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("    canTake: exception e=" + e);
13822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.reset();
1390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
140f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (DBG) log("canTake: NOT taking !!");
1410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return false;
1420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
145f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void acceptCall() throws CallStateException {
1470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
14822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if ((mRingingCall.getState() == Call.State.INCOMING) ||
14922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    (mRingingCall.getState() == Call.State.WAITING)) {
150f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("acceptCall: accepting");
1510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Always unmute when answering a new call
15222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.setMute(false);
15322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.acceptCall();
1540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
155f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
156f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("acceptCall:" +
157f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        " throw CallStateException(\"phone not ringing\")");
158f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
164f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void rejectCall() throws CallStateException {
1660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
16722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mRingingCall.getState().isRinging()) {
168f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("rejectCall: rejecting");
16922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.rejectCall();
1700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
171f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
172f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("rejectCall:" +
173f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        " throw CallStateException(\"phone not ringing\")");
174f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
180f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Connection dial(String dialString) throws CallStateException {
1820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
1830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return dialInternal(dialString);
1840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private Connection dialInternal(String dialString)
1880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throws CallStateException {
189f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("dialInternal: dialString=" + (VDBG ? dialString : "xxxxxx"));
1900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        clearDisconnected();
1910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!canDial()) {
193f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            throw new CallStateException("dialInternal: cannot dial in current state");
1940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
19522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        if (mForegroundCall.getState() == SipCall.State.ACTIVE) {
1960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switchHoldingAndActive();
1970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
19822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        if (mForegroundCall.getState() != SipCall.State.IDLE) {
1990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            //we should have failed in !canDial() above before we get here
2000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("cannot dial in current state");
2010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
20322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mForegroundCall.setMute(false);
2040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        try {
20522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            Connection c = mForegroundCall.dial(dialString);
2060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return c;
2070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } catch (SipException e) {
208f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("dialInternal: ", e);
2090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("dial error: " + e);
2100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
213f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void switchHoldingAndActive() throws CallStateException {
215f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("dialInternal: switch fg and bg");
2160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
21722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.switchWith(mBackgroundCall);
21822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mBackgroundCall.getState().isAlive()) mBackgroundCall.hold();
21922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mForegroundCall.getState().isAlive()) mForegroundCall.unhold();
2200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
223f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canConference() {
225f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("canConference: ret=true");
2260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return true;
2270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
229f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference() throws CallStateException {
2310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
23222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if ((mForegroundCall.getState() != SipCall.State.ACTIVE)
23322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    || (mForegroundCall.getState() != SipCall.State.ACTIVE)) {
2340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("wrong state to merge calls: fg="
23522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mForegroundCall.getState() + ", bg="
23622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mBackgroundCall.getState());
2370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
238f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (DBG) log("conference: merge fg & bg");
23922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.merge(mBackgroundCall);
2400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference(Call that) throws CallStateException {
2440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
2450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!(that instanceof SipCall)) {
2460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("expect " + SipCall.class
2470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ", cannot merge with " + that.getClass());
2480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
24922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.merge((SipCall) that);
2500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
253f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canTransfer() {
2550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return false;
2560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
258f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
259cbaa45bbf2cab852b6c9c3a887e9f803d4e857eaWink Saville    public void explicitCallTransfer() {
2600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        //mCT.explicitCallTransfer();
2610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
263f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void clearDisconnected() {
2650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
26622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mRingingCall.clearDisconnected();
26722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.clearDisconnected();
26822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mBackgroundCall.clearDisconnected();
2690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            updatePhoneState();
2710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyPreciseCallStateChanged();
2720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
275f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendDtmf(char c) {
2770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
278f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("sendDtmf called with invalid character '" + c + "'");
27922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        } else if (mForegroundCall.getState().isAlive()) {
2800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
28122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mForegroundCall.sendDtmf(c);
2820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
286f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void startDtmf(char c) {
2880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
289f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("startDtmf called with invalid character '" + c + "'");
2900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
2910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sendDtmf(c);
2920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
295f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void stopDtmf() {
2970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // no op
2980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendBurstDtmf(String dtmfString) {
301f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        loge("sendBurstDtmf() is a CDMA method");
3020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
304f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getOutgoingCallerIdDisplay(Message onComplete) {
3060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
3070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
311f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
3130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           Message onComplete) {
3140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what's this for SIP?
3150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
319f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getCallWaiting(Message onComplete) {
3210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
3220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
326f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setCallWaiting(boolean enable, Message onComplete) {
3280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
329f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        loge("call waiting not supported");
3300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
3330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setEchoSuppressionEnabled(boolean enabled) {
3340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // TODO: Remove the enabled argument. We should check the speakerphone
3350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // state with AudioManager instead of keeping a state here so the
3360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // method with a state argument is redundant. Also rename the method
3370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // to something like onSpeaerphoneStateChanged(). Echo suppression may
3380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // not be available on every device.
3390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
34022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.setAudioGroupMode();
3410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
344f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setMute(boolean muted) {
3460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
34722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.setMute(muted);
3480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
351f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean getMute() {
35322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return (mForegroundCall.getState().isAlive()
35422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                ? mForegroundCall.getMute()
35522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                : mBackgroundCall.getMute());
3560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
358f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getForegroundCall() {
36022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mForegroundCall;
3610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
363f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getBackgroundCall() {
36522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mBackgroundCall;
3660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
368f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getRingingCall() {
37022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mRingingCall;
3710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
373f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public ServiceState getServiceState() {
3750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: we may need to provide this when data connectivity is lost
3760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // or when server is down
3770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return super.getServiceState();
3780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getUriString(SipProfile p) {
3810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // SipProfile.getUriString() may contain "SIP:" and port
3820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return p.getUserName() + "@" + getSipDomain(p);
3830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getSipDomain(SipProfile p) {
3860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        String domain = p.getSipDomain();
3870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // TODO: move this to SipProfile
3880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (domain.endsWith(":5060")) {
3890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain.substring(0, domain.length() - 5);
3900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
3910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain;
3920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
395f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
396f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
397f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        int sessionState = sipAudioCall.getState();
398f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        switch (sessionState) {
399f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.READY_TO_CALL:            return Call.State.IDLE;
400f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.INCOMING_CALL:
401f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
402f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL:            return Call.State.DIALING;
403f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
404f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
405f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.IN_CALL:                  return Call.State.ACTIVE;
406f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            default:
407f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                slog("illegal connection state: " + sessionState);
408f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return Call.State.DISCONNECTED;
409f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
410f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
411f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
412f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void log(String s) {
413f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.d(LOG_TAG, s);
414f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
415f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
416f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static void slog(String s) {
417f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.d(LOG_TAG, s);
418f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
419f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
420f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void loge(String s) {
421f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.e(LOG_TAG, s);
422f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
423f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
424f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void loge(String s, Exception e) {
425f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.e(LOG_TAG, s, e);
426f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
427f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
4280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipCall extends SipCallBase {
429f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SC_TAG = "SipCall";
430f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SC_DBG = true;
431f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SC_VDBG = false; // STOPSHIP if true
432f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
4330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void reset() {
434f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("reset");
43522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.clear();
4360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.IDLE);
4370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void switchWith(SipCall that) {
440f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("switchWith");
4410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall tmp = new SipCall();
4430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                tmp.takeOver(this);
4440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                this.takeOver(that);
4450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                that.takeOver(tmp);
4460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void takeOver(SipCall that) {
450f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("takeOver");
45122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections = that.mConnections;
45222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mState = that.mState;
45322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
4540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).changeOwner(this);
4550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Phone getPhone() {
4600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return SipPhone.this;
4610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public List<Connection> getConnections() {
465f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_VDBG) log("getConnections");
4660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // FIXME should return Collections.unmodifiableList();
46822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                return mConnections;
4690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        Connection dial(String originalNumber) throws SipException {
473f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("dial: num=" + (SC_VDBG ? originalNumber : "xxx"));
474f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            // TODO: Should this be synchronized?
4750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String calleeSipUri = originalNumber;
4760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!calleeSipUri.contains("@")) {
4770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String replaceStr = Pattern.quote(mProfile.getUserName() + "@");
4780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr,
4790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        calleeSipUri + "@");
4800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
4820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipProfile callee =
4830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        new SipProfile.Builder(calleeSipUri).build();
4840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection c = new SipConnection(this, callee,
4850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        originalNumber);
4860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                c.dial();
48722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mConnections.add(c);
4880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(Call.State.DIALING);
4890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return c;
4900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (ParseException e) {
4910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new SipException("dial", e);
4920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
4970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
49822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                if (mState.isAlive()) {
499f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("hangup: call " + getState()
5000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
5010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(State.DISCONNECTING);
5020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    CallStateException excp = null;
50322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    for (Connection c : mConnections) {
5040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        try {
5050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            c.hangup();
5060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        } catch (CallStateException e) {
5070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            excp = e;
5080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
5090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
5100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (excp != null) throw excp;
5110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } else {
512f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("hangup: dead call " + getState()
5130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
5140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
5150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
5190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipProfile callee = sipAudioCall.getPeerProfile();
5200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipConnection c = new SipConnection(this, callee);
52122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.add(c);
5220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
5240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            c.initIncomingCall(sipAudioCall, newState);
5250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
5270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyNewRingingConnectionP(c);
5280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void rejectCall() throws CallStateException {
531f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("rejectCall:");
5320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            hangup();
5330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
536f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("acceptCall: accepting");
53722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (this != mRingingCall) {
5380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a non-ringing call");
5390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
54022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mConnections.size() != 1) {
5410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a conf call");
5420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
54322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            ((SipConnection) mConnections.get(0)).acceptCall();
5440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean isSpeakerOn() {
547f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Boolean ret = ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
5480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    .isSpeakerphoneOn();
549f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_VDBG) log("isSpeakerOn: ret=" + ret);
550f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            return ret;
5510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setAudioGroupMode() {
5540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
555f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (audioGroup == null) {
556f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("setAudioGroupMode: audioGroup == null ignore");
557f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return;
558f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
5590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int mode = audioGroup.getMode();
56022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState == State.HOLDING) {
5610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
5620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (getMute()) {
5630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_MUTED);
5640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (isSpeakerOn()) {
5650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
5660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
5670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_NORMAL);
5680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
569f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log(String.format(
570f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    "setAudioGroupMode change: %d --> %d", mode,
5710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    audioGroup.getMode()));
5720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
575f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("hold:");
5760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.HOLDING);
57722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) ((SipConnection) c).hold();
5780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
5790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold() throws CallStateException {
582f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("unhold:");
5830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.ACTIVE);
5840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = new AudioGroup();
58522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
5860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).unhold(audioGroup);
5870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
5890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
592f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("setMute: muted=" + muted);
59322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
5940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).setMute(muted);
5950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
59922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            boolean ret = mConnections.isEmpty()
6000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    ? false
60122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    : ((SipConnection) mConnections.get(0)).getMute();
602f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("getMute: ret=" + ret);
603f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            return ret;
6040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void merge(SipCall that) throws CallStateException {
607f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("merge:");
6080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
6090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // copy to an array to avoid concurrent modification as connections
6110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // in that.connections will be removed in add(SipConnection).
61222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            Connection[] cc = that.mConnections.toArray(
61322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    new Connection[that.mConnections.size()]);
6140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : cc) {
6150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection conn = (SipConnection) c;
6160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                add(conn);
6170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (conn.getState() == Call.State.HOLDING) {
6180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    conn.unhold(audioGroup);
6190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            that.setState(Call.State.IDLE);
6220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void add(SipConnection conn) {
625f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("add:");
6260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipCall call = conn.getCall();
6270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (call == this) return;
62822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (call != null) call.mConnections.remove(conn);
6290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
63022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.add(conn);
6310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            conn.changeOwner(this);
6320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void sendDtmf(char c) {
635f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("sendDtmf: c=" + c);
6360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
637f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (audioGroup == null) {
638f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("sendDtmf: audioGroup == null, ignore c=" + c);
639f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return;
640f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
6410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            audioGroup.sendDtmf(convertDtmf(c));
6420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private int convertDtmf(char c) {
6450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int code = c - '0';
6460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((code < 0) || (code > 9)) {
6470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                switch (c) {
6480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '*': return 10;
6490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '#': return 11;
6500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'A': return 12;
6510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'B': return 13;
6520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'C': return 14;
6530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'D': return 15;
6540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    default:
6550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        throw new IllegalArgumentException(
6560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                "invalid DTMF char: " + (int) c);
6570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return code;
6600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
6630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(State newState) {
66422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != newState) {
66522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                if (SC_DBG) log("setState: cur state" + mState
6660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + " --> " + newState + ": " + this + ": on phone "
66722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + getPhone() + " " + mConnections.size());
6680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (newState == Call.State.ALERTING) {
67022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    mState = newState; // need in ALERTING to enable ringback
67122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    startRingbackTone();
67222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                } else if (mState == Call.State.ALERTING) {
67322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    stopRingbackTone();
6740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
67522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mState = newState;
6760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                updatePhoneState();
6770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                notifyPreciseCallStateChanged();
6780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionStateChanged(SipConnection conn) {
6820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // this can be called back when a conf call is formed
683f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("onConnectionStateChanged: conn=" + conn);
68422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != State.ACTIVE) {
6850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(conn.getState());
6860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionEnded(SipConnection conn) {
6900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // set state to DISCONNECTED only when all conns are disconnected
691f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("onConnectionEnded: conn=" + conn);
69222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != State.DISCONNECTED) {
6930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                boolean allConnectionsDisconnected = true;
694f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("---check connections: "
69522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mConnections.size());
69622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                for (Connection c : mConnections) {
697f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("   state=" + c.getState() + ": "
6980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + c);
6990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (c.getState() != State.DISCONNECTED) {
7000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        allConnectionsDisconnected = false;
7010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        break;
7020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (allConnectionsDisconnected) setState(State.DISCONNECTED);
7050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyDisconnectP(conn);
7070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private AudioGroup getAudioGroup() {
71022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mConnections.isEmpty()) return null;
71122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            return ((SipConnection) mConnections.get(0)).getAudioGroup();
7120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
713f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
714f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
715f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SC_TAG, s);
716f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
7170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
7180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipConnection extends SipConnectionBase {
720f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SCN_TAG = "SipConnection";
721f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SCN_DBG = true;
722f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
7230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipCall mOwner;
7240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCall mSipAudioCall;
7250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private Call.State mState = Call.State.IDLE;
7260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipProfile mPeer;
7270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean mIncoming = false;
728f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private String mOriginalNumber; // may be a PSTN number
7290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
7310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            protected void onCallEnded(DisconnectCause cause) {
7330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (getDisconnectCause() != DisconnectCause.LOCAL) {
7340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setDisconnectCause(cause);
7350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
7370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(Call.State.DISCONNECTED);
7380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
739f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    // FIXME: This goes null and is synchronized, but many uses aren't sync'd
7400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mSipAudioCall = null;
7410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    String sessionState = (sipAudioCall == null)
7420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? ""
7430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : (sipAudioCall.getState() + ", ");
744f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SCN_DBG) log("[SipAudioCallAdapter] onCallEnded: "
7450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + mPeer.getUriString() + ": " + sessionState
7460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + "cause: " + getDisconnectCause() + ", on phone "
7470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + getPhone());
7480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
7490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
7500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.close();
7510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionEnded(SipConnection.this);
7530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallEstablished(SipAudioCall call) {
7580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
759f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                // Race onChanged synchronized this isn't
7600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.ACTIVE) call.startAudio();
7610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallHeld(SipAudioCall call) {
7650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
766f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                // Race onChanged synchronized this isn't
7670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.HOLDING) call.startAudio();
7680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onChanged(SipAudioCall call) {
7720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
7730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    Call.State newState = getCallStateFrom(call);
7740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (mState == newState) return;
7750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (newState == Call.State.INCOMING) {
7760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(mOwner.getState()); // INCOMING or WAITING
7770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    } else {
77822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        if (mOwner == mRingingCall) {
77922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                            if (mRingingCall.getState() == Call.State.WAITING) {
7800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                try {
7810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    switchHoldingAndActive();
7820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                } catch (CallStateException e) {
7830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    // disconnect the call.
7840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    onCallEnded(DisconnectCause.LOCAL);
7850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    return;
7860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                }
7870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            }
78822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                            mForegroundCall.switchWith(mRingingCall);
7890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
7900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(newState);
7910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionStateChanged(SipConnection.this);
793f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SCN_DBG) log("onChanged: "
7940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + mPeer.getUriString() + ": " + mState
7950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + " on phone " + getPhone());
7960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
8000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            protected void onError(DisconnectCause cause) {
801f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("onError: " + cause);
8020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onCallEnded(cause);
8030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        };
8050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee,
8070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String originalNumber) {
8080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super(originalNumber);
8090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
8100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mPeer = callee;
8110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOriginalNumber = originalNumber;
8120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee) {
8150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            this(owner, callee, getUriString(callee));
8160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getCnapName() {
8200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String displayName = mPeer.getDisplayName();
8210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return TextUtils.isEmpty(displayName) ? null
8220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                                  : displayName;
8230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public int getNumberPresentation() {
8270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return PhoneConstants.PRESENTATION_ALLOWED;
8280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
8310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
8320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = sipAudioCall;
8330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sipAudioCall.setListener(mAdapter); // call back to set state
8340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mIncoming = true;
8350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
8380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
8400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall(): " + e);
8420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void changeOwner(SipCall owner) {
8460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
8470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AudioGroup getAudioGroup() {
8500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (mSipAudioCall == null) return null;
8510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mSipAudioCall.getAudioGroup();
8520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void dial() throws SipException {
8550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.DIALING);
8560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
8570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    TIMEOUT_MAKE_CALL);
8580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setListener(mAdapter);
8590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
8620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.HOLDING);
8630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
8650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("hold(): " + e);
8670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold(AudioGroup audioGroup) throws CallStateException {
8710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setAudioGroup(audioGroup);
8720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.ACTIVE);
8730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
8750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("unhold(): " + e);
8770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
8810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) {
882f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("setState: prev muted=" + !muted + " new muted=" + muted);
8830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.toggleMute();
8840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
8880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return (mSipAudioCall == null) ? false
8890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           : mSipAudioCall.isMuted();
8900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(Call.State state) {
8940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state == mState) return;
8950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super.setState(state);
8960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mState = state;
8970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Call.State getState() {
9010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mState;
9020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public boolean isIncoming() {
9060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mIncoming;
9070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getAddress() {
9110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // Phone app uses this to query caller ID. Return the original dial
9120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // number (which may be a PSTN number) instead of the peer's SIP
9130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // URI.
9140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOriginalNumber;
9150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipCall getCall() {
9190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner;
9200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected Phone getPhone() {
9240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner.getPhone();
9250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
9290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
930f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("hangup: conn=" + mPeer.getUriString()
9310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ": " + mState + ": on phone "
9320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + getPhone().getPhoneName());
9330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (!mState.isAlive()) return;
9340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                try {
9350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
9360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
9370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
9380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.endCall();
9390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
9400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } catch (SipException e) {
9410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException("hangup(): " + e);
9420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } finally {
9430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mAdapter.onCallEnded(((mState == Call.State.INCOMING)
9440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            || (mState == Call.State.WAITING))
9450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? DisconnectCause.INCOMING_REJECTED
9460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : DisconnectCause.LOCAL);
9470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
9480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void separate() throws CallStateException {
9530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
9540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall call = (getPhone() == SipPhone.this)
95522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        ? (SipCall) getBackgroundCall()
95622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        : (SipCall) getForegroundCall();
9570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (call.getState() != Call.State.IDLE) {
9580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException(
9590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            "cannot put conn back to a call in non-idle state: "
9600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + call.getState());
9610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
962f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("separate: conn="
9630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + mPeer.getUriString() + " from " + mOwner + " back to "
9640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + call);
9650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // separate the AudioGroup and connection from the original call
9670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                Phone originalPhone = getPhone();
9680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                AudioGroup audioGroup = call.getAudioGroup(); // may be null
9690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.add(this);
9700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.setAudioGroup(audioGroup);
9710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // put the original call to bg; and the separated call becomes
9730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // fg if it was in bg
9740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                originalPhone.switchHoldingAndActive();
9750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // start audio and notify the phone app of the state change
97722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                call = (SipCall) getForegroundCall();
9780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.startAudio();
9790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.onConnectionStateChanged(this);
9800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
983f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
984f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SCN_TAG, s);
9850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
9870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
989f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SACA_TAG = "SipAudioCallAdapter";
990f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SACA_DBG = true;
9910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected abstract void onCallEnded(Connection.DisconnectCause cause);
9920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected abstract void onError(Connection.DisconnectCause cause);
9930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallEnded(SipAudioCall call) {
996f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) log("onCallEnded: call=" + call);
9970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            onCallEnded(call.isInCall()
9980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    ? Connection.DisconnectCause.NORMAL
9990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    : Connection.DisconnectCause.INCOMING_MISSED);
10000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallBusy(SipAudioCall call) {
1004f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) log("onCallBusy: call=" + call);
10050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            onCallEnded(Connection.DisconnectCause.BUSY);
10060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onError(SipAudioCall call, int errorCode,
10100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String errorMessage) {
1011f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) {
1012f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                log("onError: call=" + call + " code="+ SipErrorCode.toString(errorCode)
1013f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    + ": " + errorMessage);
1014f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
10150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switch (errorCode) {
10160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_UNREACHABLE:
10170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.SERVER_UNREACHABLE);
10180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.PEER_NOT_REACHABLE:
10200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.NUMBER_UNREACHABLE);
10210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_REMOTE_URI:
10230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.INVALID_NUMBER);
10240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TIME_OUT:
10260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TRANSACTION_TERMINTED:
10270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.TIMED_OUT);
10280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.DATA_CONNECTION_LOST:
10300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.LOST_SIGNAL);
10310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_CREDENTIALS:
10330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.INVALID_CREDENTIALS);
10340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION:
10360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.OUT_OF_NETWORK);
10370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_ERROR:
10390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.SERVER_ERROR);
10400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SOCKET_ERROR:
10420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CLIENT_ERROR:
10430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                default:
10440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    onError(Connection.DisconnectCause.ERROR_UNSPECIFIED);
10450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
10460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1047f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
1048f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
1049f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SACA_TAG, s);
1050f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
10510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
10520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville}
1053